mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-09-29 03:17:52 -04:00
[Enhancement] Time Splits v2 (#5839)
This aims to port over the Time Splits feature from 2Ship as it is cleaner & more robust. New to this is the ability to finally compare splits from a second list. Old Save data will now be converted to the new format automatically. Once it converts the data, the old save file will be renamed with a new ".backup" extension. Co-authored-by: serprex <159546+serprex@users.noreply.github.com>
This commit is contained in:
@@ -253,15 +253,6 @@ TimestampInfo itemTimestampDisplay[TIMESTAMP_MAX];
|
||||
TimestampInfo sceneTimestampDisplay[8191];
|
||||
// std::vector<TimestampInfo> sceneTimestampDisplay;
|
||||
|
||||
std::string formatTimestampGameplayStat(uint32_t value) {
|
||||
uint32_t sec = value / 10;
|
||||
uint32_t hh = sec / 3600;
|
||||
uint32_t mm = (sec - hh * 3600) / 60;
|
||||
uint32_t ss = sec - hh * 3600 - mm * 60;
|
||||
uint32_t ds = value % 10;
|
||||
return spdlog::fmt_lib::format("{}:{:0>2}:{:0>2}.{}", hh, mm, ss, ds);
|
||||
}
|
||||
|
||||
std::string formatIntGameplayStat(uint32_t value) {
|
||||
return spdlog::fmt_lib::format("{}", value);
|
||||
}
|
||||
@@ -275,7 +266,7 @@ std::string formatHexOnlyGameplayStat(uint32_t value) {
|
||||
}
|
||||
|
||||
extern "C" char* GameplayStats_GetCurrentTime() {
|
||||
std::string timeString = formatTimestampGameplayStat(static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME)).c_str();
|
||||
std::string timeString = Ship_FormatTimeDisplay(static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME)).c_str();
|
||||
const size_t stringLength = timeString.length();
|
||||
char* timeChar = (char*)malloc(stringLength + 1); // We need to use malloc so we can free this from a C file.
|
||||
strcpy(timeChar, timeString.c_str());
|
||||
@@ -449,20 +440,19 @@ void DrawGameplayStatsHeader() {
|
||||
GameplayStatsRow("Build Version:", (char*)gBuildVersion);
|
||||
}
|
||||
if (gSaveContext.ship.stats.rtaTiming) {
|
||||
GameplayStatsRow("Total Time (RTA):", formatTimestampGameplayStat(static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME)),
|
||||
GameplayStatsRow("Total Time (RTA):", Ship_FormatTimeDisplay(static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME)),
|
||||
gSaveContext.ship.stats.gameComplete ? COLOR_GREEN : COLOR_WHITE);
|
||||
} else {
|
||||
GameplayStatsRow("Total Game Time:", formatTimestampGameplayStat(static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME)),
|
||||
GameplayStatsRow("Total Game Time:", Ship_FormatTimeDisplay(static_cast<u32>(GAMEPLAYSTAT_TOTAL_TIME)),
|
||||
gSaveContext.ship.stats.gameComplete ? COLOR_GREEN : COLOR_WHITE);
|
||||
}
|
||||
if (CVarGetInteger(CVAR_GAMEPLAY_STATS("ShowAdditionalTimers"), 0)) { // !Only display total game time
|
||||
GameplayStatsRow("Gameplay Time:", formatTimestampGameplayStat(gSaveContext.ship.stats.playTimer / 2),
|
||||
GameplayStatsRow("Gameplay Time:", Ship_FormatTimeDisplay(gSaveContext.ship.stats.playTimer / 2), COLOR_GREY);
|
||||
GameplayStatsRow("Pause Menu Time:", Ship_FormatTimeDisplay(gSaveContext.ship.stats.pauseTimer / 3),
|
||||
COLOR_GREY);
|
||||
GameplayStatsRow("Pause Menu Time:", formatTimestampGameplayStat(gSaveContext.ship.stats.pauseTimer / 3),
|
||||
COLOR_GREY);
|
||||
GameplayStatsRow("Time in scene:", formatTimestampGameplayStat(gSaveContext.ship.stats.sceneTimer / 2),
|
||||
GameplayStatsRow("Time in scene:", Ship_FormatTimeDisplay(gSaveContext.ship.stats.sceneTimer / 2),
|
||||
COLOR_LIGHT_BLUE);
|
||||
GameplayStatsRow("Time in room:", formatTimestampGameplayStat(gSaveContext.ship.stats.roomTimer / 2),
|
||||
GameplayStatsRow("Time in room:", Ship_FormatTimeDisplay(gSaveContext.ship.stats.roomTimer / 2),
|
||||
COLOR_LIGHT_BLUE);
|
||||
}
|
||||
if (gPlayState != NULL && CVarGetInteger(CVAR_GAMEPLAY_STATS("ShowDebugInfo"), 0)) { // && display debug info
|
||||
@@ -494,7 +484,7 @@ void DrawGameplayStatsTimestampsTab() {
|
||||
for (int i = 0; i < TIMESTAMP_MAX; i++) {
|
||||
// To be shown, the entry must have a non-zero time and a string for its display name
|
||||
if (itemTimestampDisplay[i].time > 0 && strnlen(itemTimestampDisplay[i].name, 24) > 1) {
|
||||
GameplayStatsRow(itemTimestampDisplay[i].name, formatTimestampGameplayStat(itemTimestampDisplay[i].time),
|
||||
GameplayStatsRow(itemTimestampDisplay[i].name, Ship_FormatTimeDisplay(itemTimestampDisplay[i].time),
|
||||
itemTimestampDisplay[i].color);
|
||||
}
|
||||
}
|
||||
@@ -562,7 +552,7 @@ void DrawGameplayStatsCountsTab() {
|
||||
// If using MM Bunny Hood enhancement, show how long it's been equipped (not counting pause time)
|
||||
if (Ship_GetBunnyHoodMode() != BUNNY_HOOD_VANILLA || gSaveContext.ship.stats.count[COUNT_TIME_BUNNY_HOOD] > 0) {
|
||||
GameplayStatsRow("Bunny Hood Time:",
|
||||
formatTimestampGameplayStat(gSaveContext.ship.stats.count[COUNT_TIME_BUNNY_HOOD] / 2));
|
||||
Ship_FormatTimeDisplay(gSaveContext.ship.stats.count[COUNT_TIME_BUNNY_HOOD] / 2));
|
||||
}
|
||||
GameplayStatsRow("Rolls:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_ROLLS]));
|
||||
GameplayStatsRow("Bonks:", formatIntGameplayStat(gSaveContext.ship.stats.count[COUNT_BONKS]));
|
||||
@@ -613,7 +603,7 @@ void DrawGameplayStatsBreakdownTab() {
|
||||
TimestampInfo tsInfo = sceneTimestampDisplay[i];
|
||||
bool canShow = !tsInfo.isRoom || CVarGetInteger(CVAR_GAMEPLAY_STATS("RoomBreakdown"), 0);
|
||||
if (tsInfo.time > 0 && strnlen(tsInfo.name, 40) > 1 && canShow) {
|
||||
GameplayStatsRow(tsInfo.name, formatTimestampGameplayStat(tsInfo.time), tsInfo.color);
|
||||
GameplayStatsRow(tsInfo.name, Ship_FormatTimeDisplay(tsInfo.time), tsInfo.color);
|
||||
}
|
||||
}
|
||||
std::string toPass;
|
||||
@@ -624,7 +614,7 @@ void DrawGameplayStatsBreakdownTab() {
|
||||
} else {
|
||||
toPass = ResolveSceneID(gSaveContext.ship.stats.sceneNum, gSaveContext.ship.stats.roomNum);
|
||||
}
|
||||
GameplayStatsRow(toPass.c_str(), formatTimestampGameplayStat(CURRENT_MODE_TIMER / 2));
|
||||
GameplayStatsRow(toPass.c_str(), Ship_FormatTimeDisplay(CURRENT_MODE_TIMER / 2));
|
||||
ImGui::EndTable();
|
||||
ImGui::PopStyleVar(1);
|
||||
}
|
||||
@@ -843,6 +833,7 @@ void SetupDisplayNames() {
|
||||
strcpy(itemTimestampDisplayName[TIMESTAMP_BOSSRUSH_FINISH], "Boss Rush Finished: ");
|
||||
strcpy(itemTimestampDisplayName[TIMESTAMP_FOUND_GREG], "Greg Found: ");
|
||||
strcpy(itemTimestampDisplayName[TIMESTAMP_TRIFORCE_COMPLETED], "Triforce Completed: ");
|
||||
strcpy(itemTimestampDisplayName[TIMESTAMP_TIMESPLITS_COMPLETED], "Timesplits Completed: ");
|
||||
|
||||
// Rando items
|
||||
strcpy(itemTimestampDisplayName[TIMESTAMP_FOUND_GOHMA_SOUL], "Gohma's Soul: ");
|
||||
@@ -959,6 +950,7 @@ void SetupDisplayColors() {
|
||||
case TIMESTAMP_DEFEAT_GANONDORF:
|
||||
case TIMESTAMP_DEFEAT_GANON:
|
||||
case TIMESTAMP_TRIFORCE_COMPLETED:
|
||||
case TIMESTAMP_TIMESPLITS_COMPLETED:
|
||||
itemTimestampDisplayColor[i] = COLOR_YELLOW;
|
||||
break;
|
||||
case ITEM_SONG_STORMS:
|
||||
|
||||
@@ -19,15 +19,17 @@ char* GameplayStats_GetCurrentTime();
|
||||
// Total gameplay time is tracked in tenths of seconds
|
||||
// I.E. game time counts frames at 20fps/2, pause time counts frames at 30fps/3
|
||||
// Frame counts in z_play.c and z_kaleido_scope_call.c
|
||||
#define GAMEPLAYSTAT_TOTAL_TIME \
|
||||
(gSaveContext.ship.stats.rtaTiming \
|
||||
? (!gSaveContext.ship.stats.gameComplete \
|
||||
? (!gSaveContext.ship.stats.firstInput \
|
||||
? 0 \
|
||||
: ((GetUnixTimestamp() - gSaveContext.ship.stats.firstInput) / 100)) \
|
||||
: (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \
|
||||
? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \
|
||||
: gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED])) \
|
||||
#define GAMEPLAYSTAT_TOTAL_TIME \
|
||||
(gSaveContext.ship.stats.rtaTiming \
|
||||
? (!gSaveContext.ship.stats.gameComplete \
|
||||
? (!gSaveContext.ship.stats.firstInput \
|
||||
? 0 \
|
||||
: ((GetUnixTimestamp() - gSaveContext.ship.stats.firstInput) / 100)) \
|
||||
: (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \
|
||||
? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] \
|
||||
: (gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \
|
||||
? gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] \
|
||||
: gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TIMESPLITS_COMPLETED]))) \
|
||||
: (gSaveContext.ship.stats.playTimer / 2 + gSaveContext.ship.stats.pauseTimer / 3))
|
||||
#define CURRENT_MODE_TIMER \
|
||||
(CVarGetInteger(CVAR_GAMEPLAY_STATS("RoomBreakdown"), 0) ? gSaveContext.ship.stats.roomTimer \
|
||||
@@ -118,6 +120,7 @@ typedef enum {
|
||||
/* 0xEC */ TIMESTAMP_FOUND_ZR_BEAN_SOUL,
|
||||
/* 0xED */ TIMESTAMP_FOUND_SKELETON_KEY,
|
||||
/* 0xEE */ TIMESTAMP_FOUND_ROCS_FEATHER,
|
||||
/* 0xEF */ TIMESTAMP_TIMESPLITS_COMPLETED,
|
||||
/* 0xF0 */ TIMESTAMP_MAX
|
||||
} GameplayStatTimestamp;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,53 +2,95 @@
|
||||
|
||||
#include <ship/window/gui/GuiWindow.h>
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#ifdef __cplusplus
|
||||
class TimeSplitWindow final : public Ship::GuiWindow {
|
||||
namespace TimeSplits {
|
||||
|
||||
class TimesplitsWindow : public Ship::GuiWindow {
|
||||
public:
|
||||
using GuiWindow::GuiWindow;
|
||||
|
||||
void InitElement() override;
|
||||
void DrawElement() override{};
|
||||
void Draw() override;
|
||||
void DrawElement() override;
|
||||
void UpdateElement() override{};
|
||||
};
|
||||
|
||||
typedef enum SplitAction {
|
||||
SPLIT_ACTION_SAVE,
|
||||
SPLIT_ACTION_LOAD,
|
||||
SPLIT_ACTION_UPDATE,
|
||||
SPLIT_ACTION_COLLECT,
|
||||
SPLIT_ACTION_DELETE
|
||||
} SplitAction;
|
||||
typedef enum {
|
||||
SPLIT_HEADERS,
|
||||
SPLIT_OPACITY,
|
||||
SPLIT_HIGHLIGHT,
|
||||
SPLIT_COMPARE,
|
||||
} SplitSettings;
|
||||
|
||||
typedef enum SplitStatus {
|
||||
SPLIT_STATUS_ACTIVE,
|
||||
SPLIT_STATUS_INACTIVE,
|
||||
SPLIT_STATUS_COLLECTED,
|
||||
SPLIT_STATUS_SKIPPED,
|
||||
} SplitStatus;
|
||||
typedef enum {
|
||||
SPLIT_SAVE,
|
||||
SPLIT_LOAD,
|
||||
SPLIT_RETRIEVE,
|
||||
SPLIT_CONVERT,
|
||||
} SplitFileActions;
|
||||
|
||||
typedef enum SplitType {
|
||||
SPLIT_TYPE_ITEM,
|
||||
SPLIT_TYPE_UPGRADE,
|
||||
SPLIT_TYPE_EQUIPMENT,
|
||||
SPLIT_TYPE_QUEST,
|
||||
SPLIT_TYPE_BOSS,
|
||||
SPLIT_TYPE_ENTRANCE,
|
||||
SPLIT_TYPE_MISC
|
||||
} SplitType;
|
||||
typedef enum { SPLIT_INACTIVE, SPLIT_ACTIVE, SPLIT_COMPLETE, SPLIT_SKIPPED } SplitStatus;
|
||||
|
||||
// Split ids come from several enums, the type says which one an id belongs to.
|
||||
typedef enum {
|
||||
SPLIT_TYPE_ITEM, // ITEM_*
|
||||
SPLIT_TYPE_SCENE, // SCENE_*
|
||||
SPLIT_TYPE_BOSS, // ACTOR_* of the boss
|
||||
} SplitTypes;
|
||||
|
||||
typedef struct {
|
||||
uint32_t splitType;
|
||||
uint32_t splitID;
|
||||
uint32_t splitId;
|
||||
std::string splitName;
|
||||
std::string splitImage;
|
||||
ImVec4 splitTint;
|
||||
uint32_t splitTimeCurrent;
|
||||
uint32_t splitTimeBest;
|
||||
uint32_t splitTimePreviousBest;
|
||||
uint32_t splitTimeStatus;
|
||||
uint32_t splitSkullTokenCount;
|
||||
} SplitObject;
|
||||
uint32_t splitCurrentTime;
|
||||
uint32_t splitPreviousBest;
|
||||
uint8_t splitStatus;
|
||||
uint32_t splitType;
|
||||
} TimesplitObject;
|
||||
|
||||
#define SPLIT_BOSS(actorId, name) \
|
||||
{ actorId, name, 0, 0, SPLIT_INACTIVE, SPLIT_TYPE_BOSS }
|
||||
|
||||
typedef struct {
|
||||
uint32_t timeDisplay;
|
||||
ImVec4 colorDisplay;
|
||||
} SplitTextObject;
|
||||
|
||||
typedef struct {
|
||||
uint32_t startIndex;
|
||||
uint32_t endIndex;
|
||||
} IndexRangeObject;
|
||||
|
||||
extern std::vector<TimesplitObject> splitList;
|
||||
extern std::vector<TimesplitObject> comparisonList;
|
||||
extern std::vector<TimesplitObject> splitObjectList;
|
||||
extern std::vector<TimesplitObject> sceneObjectList;
|
||||
extern std::vector<std::string> savedLists;
|
||||
extern std::map<uint32_t, std::vector<uint32_t>> itemSubMenuList;
|
||||
extern uint32_t comparedIndex;
|
||||
extern bool shouldPopUpOpen;
|
||||
extern uint32_t GetTotalTime();
|
||||
extern ImVec4 GetSplitColor(const TimesplitObject& split);
|
||||
extern TimesplitObject GetSplitObject(uint32_t splitType, uint32_t splitId);
|
||||
extern ImVec2 GetSplitImageSize(const TimesplitObject& split);
|
||||
extern void TableCellCenteredText(ImVec4 color, const char* text);
|
||||
extern const char* GetSplitImage(const TimesplitObject& split);
|
||||
extern void SplitsPushImageButtonStyle();
|
||||
extern void SplitsPopImageButtonStyle();
|
||||
extern void HandlePopUpContext(uint32_t popupId);
|
||||
extern void HandleDragAndDrop(size_t i);
|
||||
extern void UpdateSplitBests();
|
||||
extern void UpdateSplitSettings(uint32_t settingName);
|
||||
extern void SkipSplitEntry(uint32_t index);
|
||||
extern void AddSplitEntryBySceneId(uint32_t sceneId);
|
||||
extern void AddSplitEntry(uint32_t splitType, uint32_t splitId);
|
||||
extern void RemoveSplitEntry(uint32_t index);
|
||||
extern void SplitSaveFileAction(uint32_t action, std::string listName);
|
||||
extern void DrawSplitsList(bool isMain);
|
||||
extern void SplitLoadComparisonList();
|
||||
|
||||
} // namespace TimeSplits
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
#include "TimeSplits.h"
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/SohGui/UIWidgets.hpp"
|
||||
#include "fast/Fast3dGui.h"
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <iterator>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
extern "C" {
|
||||
#include "variables.h"
|
||||
uint64_t GetUnixTimestamp();
|
||||
}
|
||||
|
||||
#define CVAR_NAME "gSettings.TimeSplits.Enable"
|
||||
#define CVAR CVarGetInteger(CVAR_NAME, 0)
|
||||
|
||||
namespace TimeSplits {
|
||||
|
||||
typedef struct {
|
||||
uint32_t splitType;
|
||||
uint32_t splitId;
|
||||
} LegacySplitId;
|
||||
|
||||
// Bosses and the magic upgrades used to be ids 256+ in the item space, saved lists still hold those.
|
||||
#define LEGACY_SPLIT_ID_BASE 256
|
||||
static const LegacySplitId legacySplitIds[] = {
|
||||
{ SPLIT_TYPE_BOSS, ACTOR_BOSS_GOMA }, { SPLIT_TYPE_BOSS, ACTOR_BOSS_DODONGO },
|
||||
{ SPLIT_TYPE_BOSS, ACTOR_BOSS_VA }, { SPLIT_TYPE_BOSS, ACTOR_BOSS_GANONDROF },
|
||||
{ SPLIT_TYPE_BOSS, ACTOR_BOSS_FD2 }, { SPLIT_TYPE_BOSS, ACTOR_BOSS_MO },
|
||||
{ SPLIT_TYPE_BOSS, ACTOR_BOSS_SST }, { SPLIT_TYPE_BOSS, ACTOR_BOSS_TW },
|
||||
{ SPLIT_TYPE_BOSS, ACTOR_BOSS_GANON }, { SPLIT_TYPE_BOSS, ACTOR_BOSS_GANON2 },
|
||||
{ SPLIT_TYPE_ITEM, ITEM_SINGLE_MAGIC }, { SPLIT_TYPE_ITEM, ITEM_DOUBLE_MAGIC },
|
||||
{ SPLIT_TYPE_ITEM, ITEM_DOUBLE_DEFENSE },
|
||||
};
|
||||
|
||||
nlohmann::json TimesplitObject_to_json(const TimesplitObject& split) {
|
||||
return nlohmann::json{
|
||||
{ "splitId", split.splitId },
|
||||
{ "splitName", split.splitName },
|
||||
{ "splitCurrentTime", split.splitCurrentTime },
|
||||
{ "splitPreviousBest", split.splitPreviousBest },
|
||||
{ "splitStatus", SPLIT_INACTIVE },
|
||||
{ "splitType", split.splitType },
|
||||
};
|
||||
}
|
||||
|
||||
TimesplitObject json_to_TimesplitObject(const nlohmann::json& jsonSplit) {
|
||||
TimesplitObject split;
|
||||
split.splitId = jsonSplit["splitId"];
|
||||
split.splitName = jsonSplit["splitName"].get<std::string>();
|
||||
split.splitCurrentTime = jsonSplit["splitCurrentTime"];
|
||||
split.splitPreviousBest = jsonSplit["splitPreviousBest"];
|
||||
split.splitStatus = jsonSplit["splitStatus"];
|
||||
if (jsonSplit.contains("splitType")) {
|
||||
split.splitType = jsonSplit["splitType"];
|
||||
} else {
|
||||
split.splitType = SPLIT_TYPE_ITEM;
|
||||
}
|
||||
|
||||
uint32_t legacyIndex = split.splitId - LEGACY_SPLIT_ID_BASE;
|
||||
if (split.splitType == SPLIT_TYPE_ITEM && split.splitId >= LEGACY_SPLIT_ID_BASE &&
|
||||
legacyIndex < std::size(legacySplitIds)) {
|
||||
split.splitType = legacySplitIds[legacyIndex].splitType;
|
||||
split.splitId = legacySplitIds[legacyIndex].splitId;
|
||||
}
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
uint32_t GetCurrentActiveSplit(const std::vector<TimesplitObject>& list) {
|
||||
for (size_t i = 0; i < splitList.size(); i++) {
|
||||
if (splitList[i].splitStatus == SPLIT_ACTIVE) {
|
||||
return (uint32_t)i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
TimesplitObject GetSplitObjectBySceneId(uint32_t sceneId) {
|
||||
TimesplitObject splitObject;
|
||||
for (auto& list : sceneObjectList) {
|
||||
if (list.splitId == sceneId) {
|
||||
splitObject = list;
|
||||
splitObject.splitType = SPLIT_TYPE_SCENE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return splitObject;
|
||||
}
|
||||
|
||||
TimesplitObject GetSplitObject(uint32_t splitType, uint32_t splitId) {
|
||||
TimesplitObject splitObject;
|
||||
for (auto& list : splitObjectList) {
|
||||
if (list.splitType == splitType && list.splitId == splitId) {
|
||||
splitObject = list;
|
||||
}
|
||||
}
|
||||
return splitObject;
|
||||
}
|
||||
|
||||
void HandlePopUpContext(uint32_t popupId) {
|
||||
auto gui = std::dynamic_pointer_cast<Fast::Fast3dGui>(Ship::Context::GetRawInstance()->GetWindow()->GetGui());
|
||||
if (shouldPopUpOpen && ImGui::BeginPopup("ItemSubMenu")) {
|
||||
std::vector<uint32_t> itemList;
|
||||
|
||||
for (auto& item : itemSubMenuList) {
|
||||
if (item.first == popupId) {
|
||||
itemList = item.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemList.size() == 0) {
|
||||
ImGui::EndPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t slotIndex = 0;
|
||||
for (auto& list : itemList) {
|
||||
TimesplitObject split = GetSplitObject(SPLIT_TYPE_ITEM, list);
|
||||
SplitsPushImageButtonStyle();
|
||||
if (ImGui::ImageButton(std::to_string(list).c_str(), gui->GetTextureByName(GetSplitImage(split)),
|
||||
GetSplitImageSize(split) * 1.5f, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0),
|
||||
GetSplitColor(split))) {
|
||||
AddSplitEntry(SPLIT_TYPE_ITEM, list);
|
||||
ImGui::CloseCurrentPopup();
|
||||
shouldPopUpOpen = false;
|
||||
}
|
||||
UIWidgets::Tooltip(split.splitName.c_str());
|
||||
SplitsPopImageButtonStyle();
|
||||
|
||||
if (slotIndex == 4) {
|
||||
slotIndex = -1;
|
||||
} else {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
slotIndex++;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void HandleDragAndDrop(size_t i) {
|
||||
auto gui = std::dynamic_pointer_cast<Fast::Fast3dGui>(Ship::Context::GetRawInstance()->GetWindow()->GetGui());
|
||||
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
|
||||
ImGui::SetDragDropPayload("SPLIT_DRAG", &i, sizeof(size_t));
|
||||
ImGui::ImageButton(std::to_string(splitList[i].splitId).c_str(),
|
||||
gui->GetTextureByName(GetSplitImage(splitList[i])), GetSplitImageSize(splitList[i]),
|
||||
ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), GetSplitColor(splitList[i]));
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SPLIT_DRAG")) {
|
||||
size_t srcIndex = *(const size_t*)payload->Data;
|
||||
if (srcIndex != i && srcIndex < splitList.size()) {
|
||||
auto item = splitList[srcIndex];
|
||||
splitList.erase(splitList.begin() + srcIndex);
|
||||
|
||||
if (srcIndex < i) {
|
||||
i--;
|
||||
}
|
||||
|
||||
splitList.insert(splitList.begin() + i, item);
|
||||
}
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
}
|
||||
|
||||
void CheckSplitsCompleted(uint32_t index) {
|
||||
if (index == splitList.size() - 1) {
|
||||
gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TIMESPLITS_COMPLETED] = GetTotalTime();
|
||||
gSaveContext.ship.stats.gameComplete = true;
|
||||
} else {
|
||||
splitList[index + 1].splitStatus = SPLIT_ACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
void AddSplitEntryBySceneId(uint32_t sceneId) {
|
||||
TimesplitObject splitObject = GetSplitObjectBySceneId(sceneId);
|
||||
|
||||
if (splitList.size() == 0) {
|
||||
splitObject.splitStatus = SPLIT_ACTIVE;
|
||||
}
|
||||
splitList.push_back(splitObject);
|
||||
}
|
||||
|
||||
void AddSplitEntry(uint32_t splitType, uint32_t splitId) {
|
||||
TimesplitObject splitObject = GetSplitObject(splitType, splitId);
|
||||
|
||||
if (splitList.size() == 0) {
|
||||
splitObject.splitStatus = SPLIT_ACTIVE;
|
||||
}
|
||||
splitList.push_back(splitObject);
|
||||
}
|
||||
|
||||
void RemoveSplitEntry(uint32_t index) {
|
||||
if (index == GetCurrentActiveSplit(splitList)) {
|
||||
CheckSplitsCompleted(index);
|
||||
}
|
||||
|
||||
splitList.erase(splitList.begin() + index);
|
||||
}
|
||||
|
||||
void SkipSplitEntry(uint32_t index) {
|
||||
if (splitList[index].splitStatus == SPLIT_ACTIVE) {
|
||||
CheckSplitsCompleted(index);
|
||||
}
|
||||
splitList[index].splitStatus = SPLIT_SKIPPED;
|
||||
}
|
||||
|
||||
void UpdateSplitBests() {
|
||||
for (auto& splits : splitList) {
|
||||
if (splits.splitCurrentTime < splits.splitPreviousBest || splits.splitPreviousBest == 0) {
|
||||
splits.splitPreviousBest = splits.splitCurrentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSplitStatus(uint32_t splitType, uint32_t splitId) {
|
||||
uint32_t activeIndex = GetCurrentActiveSplit(splitList);
|
||||
|
||||
if (activeIndex == UINT32_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (splitList[activeIndex].splitType == splitType && splitList[activeIndex].splitId == splitId) {
|
||||
splitList[activeIndex].splitCurrentTime = GetTotalTime();
|
||||
splitList[activeIndex].splitStatus = SPLIT_COMPLETE;
|
||||
CheckSplitsCompleted(activeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsUpgradeSplitDone(const TimesplitObject& split) {
|
||||
if (split.splitType != SPLIT_TYPE_ITEM) {
|
||||
return false;
|
||||
}
|
||||
switch (split.splitId) {
|
||||
case ITEM_SINGLE_MAGIC:
|
||||
return gSaveContext.isMagicAcquired;
|
||||
case ITEM_DOUBLE_MAGIC:
|
||||
return gSaveContext.isDoubleMagicAcquired;
|
||||
case ITEM_DOUBLE_DEFENSE:
|
||||
return gSaveContext.isDoubleDefenseAcquired;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSplitStatusByUpgrades() {
|
||||
uint32_t activeIndex = GetCurrentActiveSplit(splitList);
|
||||
|
||||
if (activeIndex == UINT32_MAX || !IsUpgradeSplitDone(splitList[activeIndex])) {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateSplitStatus(splitList[activeIndex].splitType, splitList[activeIndex].splitId);
|
||||
}
|
||||
|
||||
void SplitLoadComparisonList() {
|
||||
std::string filename = Ship::Context::GetPathRelativeToAppDirectory("SoHTimeSplitData.json");
|
||||
json compareFile;
|
||||
json listArray = nlohmann::json::array();
|
||||
|
||||
std::ifstream inputFile(filename);
|
||||
if (inputFile.is_open()) {
|
||||
inputFile >> compareFile;
|
||||
inputFile.close();
|
||||
}
|
||||
|
||||
if (compareFile.contains(savedLists[comparedIndex])) {
|
||||
listArray = compareFile[savedLists[comparedIndex]];
|
||||
comparisonList.clear();
|
||||
|
||||
for (auto& data : listArray) {
|
||||
comparisonList.push_back(json_to_TimesplitObject(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SplitSaveFileAction(uint32_t action, std::string listName) {
|
||||
std::string filename = "";
|
||||
|
||||
if (action == SPLIT_CONVERT) {
|
||||
filename = Ship::Context::GetPathRelativeToAppDirectory("timesplitdata.json");
|
||||
} else {
|
||||
filename = Ship::Context::GetPathRelativeToAppDirectory("SoHTimeSplitData.json");
|
||||
}
|
||||
|
||||
json saveFile;
|
||||
json listArray = nlohmann::json::array();
|
||||
|
||||
std::ifstream inputFile(filename);
|
||||
if (inputFile.is_open()) {
|
||||
inputFile >> saveFile;
|
||||
inputFile.close();
|
||||
}
|
||||
|
||||
if (action == SPLIT_SAVE) {
|
||||
for (auto& data : splitList) {
|
||||
listArray.push_back(TimesplitObject_to_json(data));
|
||||
}
|
||||
saveFile[listName] = listArray;
|
||||
|
||||
std::ofstream outputFile(filename);
|
||||
if (outputFile.is_open()) {
|
||||
outputFile << saveFile.dump(4);
|
||||
outputFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (action == SPLIT_LOAD) {
|
||||
if (saveFile.contains(listName)) {
|
||||
listArray = saveFile[listName];
|
||||
splitList.clear();
|
||||
|
||||
for (auto& data : listArray) {
|
||||
splitList.push_back(json_to_TimesplitObject(data));
|
||||
}
|
||||
splitList[0].splitStatus = SPLIT_ACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
if (action == SPLIT_RETRIEVE) {
|
||||
savedLists.clear();
|
||||
|
||||
for (auto& data : saveFile.items()) {
|
||||
if (!data.key().empty()) {
|
||||
savedLists.push_back(data.key());
|
||||
}
|
||||
}
|
||||
if (savedLists.size() == 0) {
|
||||
savedLists.push_back("Create a List First");
|
||||
}
|
||||
}
|
||||
|
||||
if (action == SPLIT_CONVERT) {
|
||||
for (auto& data : saveFile.items()) {
|
||||
splitList.clear();
|
||||
for (auto& items : data.value()) {
|
||||
if (items["splitName"].get<std::string>() == "Skulltula Token") {
|
||||
continue;
|
||||
}
|
||||
|
||||
TimesplitObject splitObject;
|
||||
splitObject.splitName = items["splitName"].get<std::string>();
|
||||
splitObject.splitCurrentTime = items["splitTimeCurrent"];
|
||||
splitObject.splitPreviousBest = items["splitTimePreviousBest"];
|
||||
splitObject.splitStatus = SPLIT_INACTIVE;
|
||||
|
||||
// 4 is the old SPLIT_TYPE_BOSS which does not exist anymore
|
||||
if (items["splitType"] == 4) {
|
||||
splitObject.splitId = ITEM_NONE;
|
||||
splitObject.splitType = SPLIT_TYPE_ITEM;
|
||||
for (auto& split : splitObjectList) {
|
||||
if (split.splitName == splitObject.splitName) {
|
||||
splitObject.splitId = split.splitId;
|
||||
splitObject.splitType = split.splitType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 5 is the old SPLIT_TYPE_ENTRANCE which is replaced by SPLIT_TYPE_SCENE
|
||||
splitObject.splitId = items["splitID"];
|
||||
splitObject.splitType = items["splitType"] == 5 ? SPLIT_TYPE_SCENE : SPLIT_TYPE_ITEM;
|
||||
}
|
||||
splitList.push_back(splitObject);
|
||||
}
|
||||
SplitSaveFileAction(SPLIT_SAVE, data.key().c_str());
|
||||
}
|
||||
splitList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterTimesplits() {
|
||||
if (!std::filesystem::exists(Ship::Context::GetPathRelativeToAppDirectory("SoHTimeSplitData.json"))) {
|
||||
json initFile;
|
||||
std::ofstream file(Ship::Context::GetPathRelativeToAppDirectory("SoHTimeSplitData.json"));
|
||||
file << initFile.dump(4);
|
||||
file.close();
|
||||
}
|
||||
|
||||
// Handles Converting any old save data into the new format.
|
||||
if (std::filesystem::exists(Ship::Context::GetPathRelativeToAppDirectory("timesplitdata.json"))) {
|
||||
SplitSaveFileAction(SPLIT_CONVERT, "");
|
||||
std::filesystem::rename(Ship::Context::GetPathRelativeToAppDirectory("timesplitdata.json"),
|
||||
Ship::Context::GetPathRelativeToAppDirectory("timesplitdata.backup"));
|
||||
}
|
||||
|
||||
SplitSaveFileAction(SPLIT_RETRIEVE, "");
|
||||
|
||||
COND_HOOK(OnItemReceive, CVAR, [](GetItemEntry itemEntry) {
|
||||
if (itemEntry.modIndex == MOD_RANDOMIZER) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemEntry.itemId == ITEM_BOMBCHUS_20 || itemEntry.itemId == ITEM_BOMBCHUS_5) {
|
||||
itemEntry.itemId = ITEM_BOMBCHU;
|
||||
}
|
||||
if (itemEntry.itemId == ITEM_STICKS_5 || itemEntry.itemId == ITEM_STICKS_10) {
|
||||
itemEntry.itemId = ITEM_STICK;
|
||||
}
|
||||
if (itemEntry.itemId == ITEM_NUTS_5 || itemEntry.itemId == ITEM_NUTS_10) {
|
||||
itemEntry.itemId = ITEM_NUT;
|
||||
}
|
||||
if (itemEntry.itemId == ITEM_SWORD_BGS) {
|
||||
if (gSaveContext.bgsFlag == 0) {
|
||||
itemEntry.itemId = ITEM_SWORD_KNIFE;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateSplitStatus(SPLIT_TYPE_ITEM, (uint32_t)itemEntry.itemId);
|
||||
});
|
||||
|
||||
COND_HOOK(OnPlayerUpdate, CVAR, []() { UpdateSplitStatusByUpgrades(); });
|
||||
|
||||
COND_HOOK(OnPlayerBottleUpdate, CVAR,
|
||||
[](int16_t contents) { UpdateSplitStatus(SPLIT_TYPE_ITEM, (uint32_t)contents); });
|
||||
COND_HOOK(OnBossDefeat, CVAR, [](void* refActor) {
|
||||
Actor* actor = (Actor*)refActor;
|
||||
UpdateSplitStatus(SPLIT_TYPE_BOSS, (uint32_t)actor->id);
|
||||
});
|
||||
|
||||
COND_HOOK(OnSceneInit, CVAR, [](int16_t sceneNum) { UpdateSplitStatus(SPLIT_TYPE_SCENE, (uint32_t)sceneNum); });
|
||||
}
|
||||
} // namespace TimeSplits
|
||||
|
||||
static RegisterShipInitFunc initFunc(TimeSplits::RegisterTimesplits, { CVAR_NAME });
|
||||
@@ -0,0 +1,737 @@
|
||||
|
||||
#include "TimeSplitsSettings.h"
|
||||
#include "TimeSplits.h"
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/SohGui/UIWidgets.hpp"
|
||||
#include "soh/SohGui/SohGui.hpp"
|
||||
#include "fast/Fast3dGui.h"
|
||||
|
||||
#include "soh/SohGui/ImGuiUtils.h"
|
||||
|
||||
namespace TimeSplits {
|
||||
|
||||
IndexRangeObject sceneRange = { 0, 77 };
|
||||
uint32_t sceneFilterIndex = 0;
|
||||
|
||||
// clang-format off
|
||||
std::vector<TimesplitObject> sceneObjectList = {
|
||||
// Dungeon Entrances
|
||||
{ SCENE_DEKU_TREE, "Inside the Deku Tree" },
|
||||
{ SCENE_DODONGOS_CAVERN, "Dodongo's Cavern" },
|
||||
{ SCENE_JABU_JABU, "Inside Jabu-Jabu's Belly" },
|
||||
{ SCENE_FOREST_TEMPLE, "Forest Temple" },
|
||||
{ SCENE_FIRE_TEMPLE, "Fire Temple" },
|
||||
{ SCENE_WATER_TEMPLE, "Water Temple" },
|
||||
{ SCENE_SPIRIT_TEMPLE, "Spirit Temple" },
|
||||
{ SCENE_SHADOW_TEMPLE, "Shadow Temple" },
|
||||
{ SCENE_BOTTOM_OF_THE_WELL, "Bottom of the Well" },
|
||||
{ SCENE_ICE_CAVERN, "Ice Cavern" },
|
||||
{ SCENE_GERUDO_TRAINING_GROUND, "Gerudo Training Ground" },
|
||||
{ SCENE_INSIDE_GANONS_CASTLE, "Inside Ganon's Castle" },
|
||||
{ SCENE_GANONS_TOWER, "Ganon's Tower" },
|
||||
{ SCENE_GANONS_TOWER_COLLAPSE_INTERIOR, "Tower Collapse" },
|
||||
{ SCENE_INSIDE_GANONS_CASTLE_COLLAPSE, "Castle Collapse" },
|
||||
|
||||
// Kokiri Forest Region
|
||||
{ SCENE_KOKIRI_FOREST, "Kokiri Forest" },
|
||||
{ SCENE_LOST_WOODS, "Lost Woods" },
|
||||
{ SCENE_SACRED_FOREST_MEADOW, "Sacred Forest Meadow" },
|
||||
{ SCENE_KOKIRI_SHOP, "Kokiri Shop" },
|
||||
{ SCENE_LINKS_HOUSE, "Link's House" },
|
||||
{ SCENE_KNOW_IT_ALL_BROS_HOUSE, "Know-It-All Bros' House" },
|
||||
{ SCENE_TWINS_HOUSE, "Twins' House" },
|
||||
{ SCENE_MIDOS_HOUSE, "Mido's House" },
|
||||
{ SCENE_SARIAS_HOUSE, "Saria's House" },
|
||||
|
||||
// Hyrule Field & Lon Lon Ranch Region
|
||||
{ SCENE_HYRULE_FIELD, "Hyrule Field" },
|
||||
{ SCENE_MARKET_DAY, "Market (Day)" },
|
||||
{ SCENE_MARKET_NIGHT, "Market (Night)" },
|
||||
{ SCENE_MARKET_RUINS, "Market (Adult)" },
|
||||
{ SCENE_HYRULE_CASTLE, "Hyrule Castle" },
|
||||
{ SCENE_OUTSIDE_GANONS_CASTLE, "Outside Ganon's Castle" },
|
||||
{ SCENE_TEMPLE_OF_TIME, "Temple of Time" },
|
||||
{ SCENE_LON_LON_RANCH, "Lon Lon Ranch" },
|
||||
{ SCENE_LON_LON_BUILDINGS, "Ranch House" },
|
||||
{ SCENE_STABLE, "Stable" },
|
||||
{ SCENE_CASTLE_COURTYARD_ZELDA, "Castle Courtyard" },
|
||||
{ SCENE_SHOOTING_GALLERY, "Shooting Gallery" },
|
||||
{ SCENE_BAZAAR, "Bazaar" },
|
||||
{ SCENE_BOMBCHU_SHOP, "Bombchu Shop" },
|
||||
{ SCENE_HAPPY_MASK_SHOP, "Happy Mask Shop" },
|
||||
{ SCENE_TREASURE_BOX_SHOP, "Treasure Box Shop" },
|
||||
{ SCENE_POTION_SHOP_MARKET, "Market Potion Shop" },
|
||||
{ SCENE_BOMBCHU_BOWLING_ALLEY, "Bombchu Bowling Alley" },
|
||||
{ SCENE_MARKET_GUARD_HOUSE, "Guard House" },
|
||||
{ SCENE_DOG_LADY_HOUSE, "Richard's House" },
|
||||
{ SCENE_BACK_ALLEY_HOUSE, "Man in Green's House" },
|
||||
|
||||
// Kakariko & Death Mountain Region
|
||||
{ SCENE_KAKARIKO_VILLAGE, "Kakariko Village" },
|
||||
{ SCENE_GRAVEYARD, "Graveyard" },
|
||||
{ SCENE_HOUSE_OF_SKULLTULA, "House of Skulltula" },
|
||||
{ SCENE_DEATH_MOUNTAIN_TRAIL, "Death Mountain Trail" },
|
||||
{ SCENE_DEATH_MOUNTAIN_CRATER, "Death Mountain Crater" },
|
||||
{ SCENE_GORON_CITY, "Goron City" },
|
||||
{ SCENE_KAKARIKO_CENTER_GUEST_HOUSE, "Carpenter Boss's House" },
|
||||
{ SCENE_POTION_SHOP_KAKARIKO, "Kakariko Potion Shop" },
|
||||
{ SCENE_POTION_SHOP_GRANNY, "Granny's Potion Shop" },
|
||||
{ SCENE_IMPAS_HOUSE, "Impa's House" },
|
||||
{ SCENE_GRAVEKEEPERS_HUT, "Gravekeeper's Hut" },
|
||||
{ SCENE_GRAVE_WITH_FAIRYS_FOUNTAIN, "Fairy Fountain Grave" },
|
||||
{ SCENE_ROYAL_FAMILYS_TOMB, "Royal Family's Tomb" },
|
||||
{ SCENE_WINDMILL_AND_DAMPES_GRAVE, "Dampe's Grave" },
|
||||
{ SCENE_REDEAD_GRAVE, "Redead Grave" },
|
||||
{ SCENE_GORON_SHOP, "Goron Shop" },
|
||||
|
||||
// Lake Hylia & Gerudo Valley Region
|
||||
{ SCENE_LAKE_HYLIA, "Lake Hylia" },
|
||||
{ SCENE_GERUDO_VALLEY, "Gerudo Valley" },
|
||||
{ SCENE_DESERT_COLOSSUS, "Desert Colossus" },
|
||||
{ SCENE_GERUDOS_FORTRESS, "Gerudo's Fortress" },
|
||||
{ SCENE_THIEVES_HIDEOUT, "Thieves' Hideout" },
|
||||
{ SCENE_HAUNTED_WASTELAND, "Haunted Wasteland" },
|
||||
{ SCENE_FISHING_POND, "Fishing Pond" },
|
||||
{ SCENE_LAKESIDE_LABORATORY, "Lakeside Lab" },
|
||||
{ SCENE_CARPENTERS_TENT, "Carpenters' Tent" },
|
||||
|
||||
// Zora Region
|
||||
{ SCENE_ZORAS_RIVER, "Zora's River" },
|
||||
{ SCENE_ZORAS_DOMAIN, "Zora's Domain" },
|
||||
{ SCENE_ZORAS_FOUNTAIN, "Zora's Fountain" },
|
||||
{ SCENE_ZORA_SHOP, "Zora Shop" },
|
||||
|
||||
// Miscellaneous Areas
|
||||
{ SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC, "Great Fairy" },
|
||||
{ SCENE_GREAT_FAIRYS_FOUNTAIN_SPELLS, "Great Fairy" },
|
||||
{ SCENE_FAIRYS_FOUNTAIN, "Fairy Fountain" },
|
||||
{ SCENE_GROTTOS, "Grotto" },
|
||||
};
|
||||
|
||||
std::vector<TimesplitObject> splitObjectList = {
|
||||
// Equipment
|
||||
{ ITEM_SWORD_KOKIRI, "Kokiri Sword" },
|
||||
{ ITEM_SWORD_MASTER, "Master Sword" },
|
||||
{ ITEM_SWORD_BGS, "Biggoron's Sword" },
|
||||
{ ITEM_SHIELD_DEKU, "Deku Shield" },
|
||||
{ ITEM_SHIELD_HYLIAN, "Hylian Shield" },
|
||||
{ ITEM_SHIELD_MIRROR, "Mirror Shield" },
|
||||
{ ITEM_TUNIC_GORON, "Goron Tunic" },
|
||||
{ ITEM_TUNIC_ZORA, "Zora Tunic" },
|
||||
{ ITEM_BOOTS_IRON, "Iron Boots" },
|
||||
{ ITEM_BOOTS_HOVER, "Hover Boots" },
|
||||
{ ITEM_BRACELET, "Goron's Bracelet" },
|
||||
{ ITEM_SCALE_SILVER, "Silver Scale" },
|
||||
{ ITEM_SINGLE_MAGIC, "Magic" },
|
||||
{ ITEM_WALLET_ADULT, "Adult's Wallet" },
|
||||
{ ITEM_DOUBLE_DEFENSE, "Double Defense" },
|
||||
|
||||
// Inventory
|
||||
{ ITEM_STICK, "Deku Stick" },
|
||||
{ ITEM_NUT, "Deku Nut" },
|
||||
{ ITEM_BOMB, "Bomb" },
|
||||
{ ITEM_BOW, "Fairy Bow" },
|
||||
{ ITEM_ARROW_FIRE, "Fire Arrow" },
|
||||
{ ITEM_DINS_FIRE, "Din's Fire" },
|
||||
{ ITEM_SLINGSHOT, "Slingshot" },
|
||||
{ ITEM_OCARINA_FAIRY, "Fairy Ocarina" },
|
||||
{ ITEM_BOMBCHU, "Bombchu" },
|
||||
{ ITEM_HOOKSHOT, "Hookshot" },
|
||||
{ ITEM_ARROW_ICE, "Ice Arrow" },
|
||||
{ ITEM_FARORES_WIND, "Farore's Wind" },
|
||||
{ ITEM_BOOMERANG, "Boomerang" },
|
||||
{ ITEM_LENS, "Lens of Truth" },
|
||||
{ ITEM_BEAN, "Magic Bean" },
|
||||
{ ITEM_HAMMER, "Megaton Hammer" },
|
||||
{ ITEM_ARROW_LIGHT, "Light Arrow" },
|
||||
{ ITEM_NAYRUS_LOVE, "Nayru's Love" },
|
||||
{ ITEM_BOTTLE, "Empty Bottle" },
|
||||
{ ITEM_POCKET_EGG, "Pocket Egg" },
|
||||
{ ITEM_WEIRD_EGG, "Weird Egg" },
|
||||
|
||||
// Quest
|
||||
{ ITEM_SONG_LULLABY, "Zelda's Lullaby" },
|
||||
{ ITEM_SONG_EPONA, "Epona's Song" },
|
||||
{ ITEM_SONG_SARIA, "Saria's Song" },
|
||||
{ ITEM_SONG_SUN, "Sun's Song" },
|
||||
{ ITEM_SONG_TIME, "Song of Time" },
|
||||
{ ITEM_SONG_STORMS, "Song of Storms" },
|
||||
{ ITEM_SONG_MINUET, "Minuet of Forest" },
|
||||
{ ITEM_SONG_BOLERO, "Bolero of Fire" },
|
||||
{ ITEM_SONG_SERENADE, "Serenade of Water" },
|
||||
{ ITEM_SONG_REQUIEM, "Requiem of Spirit" },
|
||||
{ ITEM_SONG_NOCTURNE, "Nocturne of Shadow" },
|
||||
{ ITEM_SONG_PRELUDE, "Prelude of Light" },
|
||||
{ ITEM_MEDALLION_FOREST, "Forest Medallion" },
|
||||
{ ITEM_MEDALLION_FIRE, "Fire Medallion" },
|
||||
{ ITEM_MEDALLION_WATER, "Water Medallion" },
|
||||
{ ITEM_MEDALLION_SPIRIT, "Spirit Medallion" },
|
||||
{ ITEM_MEDALLION_SHADOW, "Shadow Medallion" },
|
||||
{ ITEM_MEDALLION_LIGHT, "Light Medallion" },
|
||||
{ ITEM_KOKIRI_EMERALD, "Kokiri's Emerald" },
|
||||
{ ITEM_GORON_RUBY, "Goron's Ruby" },
|
||||
{ ITEM_ZORA_SAPPHIRE, "Zora's Sapphire" },
|
||||
|
||||
// Bosses
|
||||
SPLIT_BOSS(ACTOR_BOSS_GOMA, "Queen Gohma"),
|
||||
SPLIT_BOSS(ACTOR_BOSS_DODONGO, "King Dodongo"),
|
||||
SPLIT_BOSS(ACTOR_BOSS_VA, "Barinade"),
|
||||
SPLIT_BOSS(ACTOR_BOSS_GANONDROF, "Phantom Ganon"),
|
||||
SPLIT_BOSS(ACTOR_BOSS_MO, "Morpha"),
|
||||
SPLIT_BOSS(ACTOR_BOSS_FD2, "Volvagia"),
|
||||
SPLIT_BOSS(ACTOR_BOSS_SST, "Bongo Bongo"),
|
||||
SPLIT_BOSS(ACTOR_BOSS_TW, "Twinrova"),
|
||||
SPLIT_BOSS(ACTOR_BOSS_GANON, "Ganondorf"),
|
||||
SPLIT_BOSS(ACTOR_BOSS_GANON2, "Ganon"),
|
||||
|
||||
// Upgrade Items
|
||||
{ ITEM_OCARINA_TIME, "Ocarina of Time" },
|
||||
{ ITEM_LONGSHOT, "Longshot" },
|
||||
{ ITEM_BULLET_BAG_40, "Big Bullet Bag" },
|
||||
{ ITEM_BULLET_BAG_50, "Biggest Bullet Bag" },
|
||||
{ ITEM_QUIVER_40, "Big Quiver" },
|
||||
{ ITEM_QUIVER_50, "Biggest Quiver" },
|
||||
{ ITEM_BOMB_BAG_20, "Bomb Bag" },
|
||||
{ ITEM_BOMB_BAG_30, "Big Bomb Bag" },
|
||||
{ ITEM_BOMB_BAG_40, "Biggest Bomb Bag" },
|
||||
{ ITEM_WALLET_GIANT, "Giant's Wallet" },
|
||||
{ ITEM_GAUNTLETS_SILVER, "Silver Gauntlets" },
|
||||
{ ITEM_GAUNTLETS_GOLD, "Gold Gauntlets" },
|
||||
{ ITEM_SCALE_GOLDEN, "Gold Scale" },
|
||||
{ ITEM_SWORD_KNIFE, "Giant's Knife" },
|
||||
{ ITEM_DOUBLE_MAGIC, "Double Magic" },
|
||||
|
||||
// Trade Items
|
||||
{ ITEM_CHICKEN, "Chicken" },
|
||||
{ ITEM_LETTER_ZELDA, "Zelda's Letter" },
|
||||
{ ITEM_MASK_KEATON, "Keaton Mask" },
|
||||
{ ITEM_MASK_SKULL, "Skull Mask" },
|
||||
{ ITEM_MASK_SPOOKY, "Spooky Mask" },
|
||||
{ ITEM_MASK_BUNNY, "Bunny Hood" },
|
||||
{ ITEM_MASK_GORON, "Goron Mask" },
|
||||
{ ITEM_MASK_ZORA, "Zora Mask" },
|
||||
{ ITEM_MASK_GERUDO, "Gerudo Mask" },
|
||||
{ ITEM_MASK_TRUTH, "Mask of Truth" },
|
||||
{ ITEM_POCKET_CUCCO, "Pocket Cucco" },
|
||||
{ ITEM_COJIRO, "Cojiro" },
|
||||
{ ITEM_ODD_MUSHROOM, "Odd Mushroom" },
|
||||
{ ITEM_ODD_POTION, "Odd Potion" },
|
||||
{ ITEM_SAW, "Poacher's Saw" },
|
||||
{ ITEM_SWORD_BROKEN, "Broke Goron Sword" },
|
||||
{ ITEM_PRESCRIPTION, "Prescription" },
|
||||
{ ITEM_FROG, "Eyeball Frog" },
|
||||
{ ITEM_EYEDROPS, "Eye Drops" },
|
||||
{ ITEM_CLAIM_CHECK, "Claim Check" },
|
||||
|
||||
|
||||
// Bottled Items
|
||||
{ ITEM_POTION_RED, "Red Potion" },
|
||||
{ ITEM_POTION_GREEN, "Green Potion" },
|
||||
{ ITEM_POTION_BLUE, "Blue Potion" },
|
||||
{ ITEM_FAIRY, "Fairy" },
|
||||
{ ITEM_FISH, "Fish" },
|
||||
{ ITEM_MILK_BOTTLE, "Milk" },
|
||||
{ ITEM_LETTER_RUTO, "Ruto's Letter" },
|
||||
{ ITEM_BLUE_FIRE, "Blue Fire" },
|
||||
{ ITEM_BUG, "Bugs" },
|
||||
{ ITEM_BIG_POE, "Big Poe" },
|
||||
{ ITEM_MILK_HALF, "Milk (Half)" },
|
||||
{ ITEM_POE, "Poe" },
|
||||
};
|
||||
|
||||
std::map<uint32_t, std::vector<uint32_t>> itemSubMenuList = {
|
||||
{ ITEM_BOW, { ITEM_BOW, ITEM_QUIVER_40, ITEM_QUIVER_50 } },
|
||||
{ ITEM_BOMB, { ITEM_BOMB_BAG_20, ITEM_BOMB_BAG_30, ITEM_BOMB_BAG_40 } },
|
||||
{ ITEM_OCARINA_FAIRY, { ITEM_OCARINA_FAIRY, ITEM_OCARINA_TIME } },
|
||||
{ ITEM_WEIRD_EGG, { ITEM_WEIRD_EGG, ITEM_CHICKEN, ITEM_LETTER_ZELDA, ITEM_MASK_KEATON, ITEM_MASK_SKULL, ITEM_MASK_SPOOKY,
|
||||
ITEM_MASK_BUNNY, ITEM_MASK_GORON, ITEM_MASK_ZORA, ITEM_MASK_GERUDO, ITEM_MASK_TRUTH } },
|
||||
{ ITEM_POCKET_EGG, { ITEM_POCKET_EGG, ITEM_POCKET_CUCCO, ITEM_COJIRO, ITEM_ODD_MUSHROOM, ITEM_ODD_POTION,
|
||||
ITEM_SAW, ITEM_SWORD_BROKEN, ITEM_PRESCRIPTION, ITEM_FROG, ITEM_EYEDROPS, ITEM_CLAIM_CHECK } },
|
||||
{ ITEM_BOTTLE, { ITEM_BOTTLE, ITEM_POTION_RED, ITEM_POTION_GREEN, ITEM_POTION_BLUE, ITEM_FAIRY, ITEM_FISH,
|
||||
ITEM_MILK_BOTTLE, ITEM_LETTER_RUTO, ITEM_BLUE_FIRE, ITEM_BUG, ITEM_BIG_POE, ITEM_MILK_HALF, ITEM_POE } },
|
||||
{ ITEM_SCALE_SILVER, { ITEM_SCALE_SILVER, ITEM_SCALE_GOLDEN } },
|
||||
{ ITEM_BRACELET, { ITEM_BRACELET, ITEM_GAUNTLETS_SILVER, ITEM_GAUNTLETS_GOLD } },
|
||||
{ ITEM_WALLET_ADULT, { ITEM_WALLET_ADULT, ITEM_WALLET_GIANT } },
|
||||
{ ITEM_SWORD_BGS, { ITEM_SWORD_KNIFE, ITEM_SWORD_BGS } },
|
||||
{ ITEM_SINGLE_MAGIC, { ITEM_SINGLE_MAGIC, ITEM_DOUBLE_MAGIC } },
|
||||
};
|
||||
|
||||
static std::map<uint32_t, ImVec4> songColorMap = {
|
||||
{ ITEM_SONG_LULLABY, { 0.878f, 0.420f, 1.0f, 1.0f } },
|
||||
{ ITEM_SONG_EPONA, { 1.0f, 0.765f, 0.235f, 1.0f } },
|
||||
{ ITEM_SONG_SARIA, { 0.498f, 1.0f, 0.537f, 1.0f } },
|
||||
{ ITEM_SONG_SUN, { 1.0f, 1.0f, 0.235f, 1.0f } },
|
||||
{ ITEM_SONG_TIME, { 0.467f, 0.925f, 1.0f, 1.0f } },
|
||||
{ ITEM_SONG_STORMS, { 0.647f, 0.647f, 0.647f, 1.0f } },
|
||||
{ ITEM_SONG_MINUET, { 0.588f, 1.0f, 0.392f, 1.0f } },
|
||||
{ ITEM_SONG_BOLERO, { 1.0f, 0.314f, 0.157f, 1.0f } },
|
||||
{ ITEM_SONG_SERENADE, { 0.392f, 0.588f, 1.0f, 1.0f } },
|
||||
{ ITEM_SONG_REQUIEM, { 1.0f, 0.627f, 0.0f, 1.0f } },
|
||||
{ ITEM_SONG_NOCTURNE, { 1.0f, 0.392f, 1.0f, 1.0f } },
|
||||
{ ITEM_SONG_PRELUDE, { 1.0f, 0.941f, 0.392f, 1.0f } },
|
||||
};
|
||||
|
||||
static std::map<uint32_t, ImVec4> bossColorMap = {
|
||||
{ ACTOR_BOSS_GOMA, UIWidgets::ColorValues.at(UIWidgets::Colors::Green) },
|
||||
{ ACTOR_BOSS_DODONGO, UIWidgets::ColorValues.at(UIWidgets::Colors::Red) },
|
||||
{ ACTOR_BOSS_VA, UIWidgets::ColorValues.at(UIWidgets::Colors::Blue) },
|
||||
{ ACTOR_BOSS_GANONDROF, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkGreen) },
|
||||
{ ACTOR_BOSS_FD2, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkRed) },
|
||||
{ ACTOR_BOSS_MO, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkBlue) },
|
||||
{ ACTOR_BOSS_SST, UIWidgets::ColorValues.at(UIWidgets::Colors::Purple) },
|
||||
{ ACTOR_BOSS_TW, UIWidgets::ColorValues.at(UIWidgets::Colors::Orange) },
|
||||
{ ACTOR_BOSS_GANON, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkGray) },
|
||||
{ ACTOR_BOSS_GANON2, UIWidgets::ColorValues.at(UIWidgets::Colors::Yellow) },
|
||||
};
|
||||
|
||||
static std::vector<const char*> sceneAreaNameMap = {
|
||||
"All Scenes",
|
||||
"Dungeons",
|
||||
"Kokiri Forest",
|
||||
"Hyrule Field & Lon Lon Ranch",
|
||||
"Kakariko & Death Mountain",
|
||||
"Lake Hylia & Gerudo Valley",
|
||||
"Zora's River",
|
||||
"Miscellaneous",
|
||||
};
|
||||
|
||||
static std::unordered_map<const char*, IndexRangeObject> sceneAreaRangeMap = {
|
||||
{ "All Scenes", { SCENE_DEKU_TREE, SCENE_GROTTOS } },
|
||||
{ "Dungeons", { SCENE_DEKU_TREE, SCENE_INSIDE_GANONS_CASTLE_COLLAPSE } },
|
||||
{ "Kokiri Forest", { SCENE_KOKIRI_FOREST, SCENE_SARIAS_HOUSE } },
|
||||
{ "Hyrule Field & Lon Lon Ranch", { SCENE_HYRULE_FIELD, SCENE_BACK_ALLEY_HOUSE } },
|
||||
{ "Kakariko & Death Mountain", { SCENE_KAKARIKO_VILLAGE, SCENE_GORON_SHOP } },
|
||||
{ "Lake Hylia & Gerudo Valley", { SCENE_LAKE_HYLIA, SCENE_CARPENTERS_TENT } },
|
||||
{ "Zora's River", { SCENE_ZORAS_RIVER, SCENE_ZORA_SHOP } },
|
||||
{ "Miscellaneous", { SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC, SCENE_GROTTOS } },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
IndexRangeObject GetSceneIndexRange(uint32_t start, uint32_t end) {
|
||||
IndexRangeObject setRange = { 0, 0 };
|
||||
|
||||
for (size_t i = 0; i < sceneObjectList.size(); i++) {
|
||||
if (sceneObjectList[i].splitId == start) {
|
||||
setRange.startIndex = static_cast<int>(i);
|
||||
}
|
||||
if (sceneObjectList[i].splitId == end) {
|
||||
setRange.endIndex = static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
|
||||
return setRange;
|
||||
}
|
||||
|
||||
IndexRangeObject GetIndexRange(uint32_t splitType, uint32_t start, uint32_t end) {
|
||||
IndexRangeObject setRange = { 0, 0 };
|
||||
|
||||
for (size_t i = 0; i < splitObjectList.size(); i++) {
|
||||
if (splitObjectList[i].splitType != splitType) {
|
||||
continue;
|
||||
}
|
||||
if (splitObjectList[i].splitId == start) {
|
||||
setRange.startIndex = static_cast<int>(i);
|
||||
}
|
||||
if (splitObjectList[i].splitId == end) {
|
||||
setRange.endIndex = static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
|
||||
return setRange;
|
||||
}
|
||||
|
||||
bool shouldPopUpOpen = false;
|
||||
uint32_t popupItem = 0;
|
||||
const char* popupTooltip = "";
|
||||
IndexRangeObject range = GetIndexRange(SPLIT_TYPE_ITEM, ITEM_SWORD_KOKIRI, ITEM_DOUBLE_DEFENSE);
|
||||
std::string listName = "Equipment";
|
||||
uint32_t listColumns = 3;
|
||||
std::string listInputName;
|
||||
std::vector<std::string> savedLists;
|
||||
uint32_t selectedIndex = 0;
|
||||
uint32_t comparedIndex = 0;
|
||||
|
||||
static bool IsSongSplit(const TimesplitObject& split) {
|
||||
return split.splitType == SPLIT_TYPE_ITEM && split.splitId >= ITEM_SONG_MINUET && split.splitId <= ITEM_SONG_STORMS;
|
||||
}
|
||||
|
||||
const char* GetSplitImage(const TimesplitObject& split) {
|
||||
if (split.splitType == SPLIT_TYPE_SCENE) {
|
||||
return "gPauseUnusedCursorTex";
|
||||
}
|
||||
if (split.splitType == SPLIT_TYPE_BOSS) {
|
||||
return "gWTriforcePieceTex";
|
||||
}
|
||||
switch (split.splitId) {
|
||||
case ITEM_SINGLE_MAGIC:
|
||||
return GetTextureForItemId(ITEM_MAGIC_SMALL);
|
||||
case ITEM_DOUBLE_MAGIC:
|
||||
return GetTextureForItemId(ITEM_MAGIC_LARGE);
|
||||
case ITEM_DOUBLE_DEFENSE:
|
||||
return GetTextureForItemId(ITEM_HEART_CONTAINER);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (IsSongSplit(split)) {
|
||||
return "gSongNoteTex";
|
||||
}
|
||||
const char* itemTexture = GetTextureForItemId(split.splitId);
|
||||
return itemTexture != nullptr ? itemTexture : GetTextureForItemId(ITEM_SOLD_OUT);
|
||||
}
|
||||
|
||||
ImVec4 GetSplitColor(const TimesplitObject& split) {
|
||||
if (split.splitType == SPLIT_TYPE_BOSS) {
|
||||
return bossColorMap.at(split.splitId);
|
||||
}
|
||||
if (IsSongSplit(split)) {
|
||||
return songColorMap.at(split.splitId);
|
||||
}
|
||||
return { 1, 1, 1, 1 };
|
||||
}
|
||||
|
||||
ImVec2 GetSplitImageSize(const TimesplitObject& split) {
|
||||
float defaultImageSize = 32.0f;
|
||||
if (IsSongSplit(split)) {
|
||||
return ImVec2(defaultImageSize / 1.5f, defaultImageSize);
|
||||
}
|
||||
return ImVec2(defaultImageSize, defaultImageSize);
|
||||
}
|
||||
|
||||
void DrawOptions() {
|
||||
if (ImGui::BeginTable("Options", 3)) {
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
UIWidgets::CVarCheckbox("Enable Time Splits", "gSettings.TimeSplits.Enable",
|
||||
{
|
||||
.color = THEME_COLOR,
|
||||
});
|
||||
UIWidgets::Tooltip("Enables the Time Split system, splits will not occur with this unchecked.");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (UIWidgets::CVarCheckbox("Show Headers", "gSettings.TimeSplits.ShowHeaders",
|
||||
{
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
UpdateSplitSettings(SPLIT_HEADERS);
|
||||
};
|
||||
UIWidgets::Tooltip("Shows the column names in the Split List.");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (UIWidgets::CVarCheckbox("Hide Background", "gSettings.TimeSplits.Opacity",
|
||||
{
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
UpdateSplitSettings(SPLIT_OPACITY);
|
||||
};
|
||||
UIWidgets::Tooltip("Hides the background of the Splits List.\n"
|
||||
"Note: The background will display if the window extrudes from the main game window.");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
UIWidgets::CVarCheckbox("Highlight Active Split", "gSettings.TimeSplits.Highlight",
|
||||
{
|
||||
.color = THEME_COLOR,
|
||||
});
|
||||
UIWidgets::Tooltip("Highlights the row with the current Active Split.");
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
UIWidgets::CVarCheckbox("Follow Active Split", "gSettings.TimeSplits.Follow",
|
||||
{
|
||||
.color = THEME_COLOR,
|
||||
});
|
||||
UIWidgets::Tooltip("Forces the Split List window to keep the Active Split visible.\n"
|
||||
"Note: This prevents user scrolling, disable to restore control.");
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
UIWidgets::CVarCheckbox("Compare Splits", "gSettings.TimeSplits.Compare",
|
||||
{
|
||||
.color = THEME_COLOR,
|
||||
});
|
||||
UIWidgets::Tooltip("Enables Split Comparisons between lists, this will integrate within the Split List.");
|
||||
|
||||
if (CVarGetInteger("gSettings.TimeSplits.Compare", 0)) {
|
||||
ImGui::SameLine();
|
||||
UIWidgets::PushStyleCombobox(THEME_COLOR);
|
||||
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (ImGui::BeginCombo("##compareSplits", savedLists[comparedIndex].c_str())) {
|
||||
for (uint32_t i = 0; i < savedLists.size(); i++) {
|
||||
if (ImGui::Selectable(savedLists[i].c_str())) {
|
||||
comparedIndex = i;
|
||||
SplitLoadComparisonList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
UIWidgets::PopStyleCombobox();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawActionButtons() {
|
||||
if (ImGui::BeginTable("Action Buttons", 2)) {
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
UIWidgets::InputString("New List", &listInputName,
|
||||
{
|
||||
.labelPosition = UIWidgets::LabelPositions::None,
|
||||
.color = THEME_COLOR,
|
||||
.placeholder = "Enter new list name",
|
||||
});
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (UIWidgets::Button("Create List", {
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
if (!listInputName.empty()) {
|
||||
SplitSaveFileAction(SPLIT_SAVE, listInputName);
|
||||
SplitSaveFileAction(SPLIT_RETRIEVE, "");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
UIWidgets::PushStyleCombobox(THEME_COLOR);
|
||||
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (ImGui::BeginCombo("##savedSplits", savedLists[selectedIndex].c_str())) {
|
||||
for (uint32_t i = 0; i < savedLists.size(); i++) {
|
||||
if (ImGui::Selectable(savedLists[i].c_str())) {
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
UIWidgets::PopStyleCombobox();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (UIWidgets::Button("Save Splits", {
|
||||
.size = { (ImGui::GetContentRegionAvail().x * 0.5f), 0 },
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
if (savedLists[0] != "Create a List First") {
|
||||
SplitSaveFileAction(SPLIT_SAVE, savedLists[selectedIndex]);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (UIWidgets::Button("Load Splits", {
|
||||
.size = { (ImGui::GetContentRegionAvail().x), 0 },
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
if (savedLists[0] != "Create a List First") {
|
||||
SplitSaveFileAction(SPLIT_LOAD, savedLists[selectedIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (UIWidgets::Button("New Attempt", {
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
for (auto& splits : splitList) {
|
||||
splits.splitStatus = SPLIT_INACTIVE;
|
||||
}
|
||||
if (splitList.size() != 0) {
|
||||
splitList[0].splitStatus = SPLIT_ACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (UIWidgets::Button("Update Splits", {
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
UpdateSplitBests();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawEntranceList() {
|
||||
auto gui = std::dynamic_pointer_cast<Fast::Fast3dGui>(Ship::Context::GetRawInstance()->GetWindow()->GetGui());
|
||||
UIWidgets::PushStyleCombobox(THEME_COLOR);
|
||||
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (ImGui::BeginCombo("##SceneFilter", sceneAreaNameMap[sceneFilterIndex])) {
|
||||
for (uint32_t i = 0; i < sceneAreaNameMap.size(); i++) {
|
||||
if (ImGui::Selectable(sceneAreaNameMap[i])) {
|
||||
sceneFilterIndex = i;
|
||||
sceneRange = GetSceneIndexRange(sceneAreaRangeMap.at(sceneAreaNameMap[i]).startIndex,
|
||||
sceneAreaRangeMap.at(sceneAreaNameMap[i]).endIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
UIWidgets::PopStyleCombobox();
|
||||
|
||||
if (ImGui::BeginChild("Entrance List")) {
|
||||
if (ImGui::BeginTable("Entrances", 2)) {
|
||||
for (uint32_t i = sceneRange.startIndex; i <= sceneRange.endIndex; i++) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(sceneObjectList[i].splitId);
|
||||
SplitsPushImageButtonStyle();
|
||||
|
||||
if (ImGui::ImageButton(std::to_string(sceneObjectList[i].splitId).c_str(),
|
||||
gui->GetTextureByName("gPauseUnusedCursorTex"), ImVec2(32.0f, 32.0f))) {
|
||||
AddSplitEntryBySceneId(sceneObjectList[i].splitId);
|
||||
};
|
||||
ImGui::SameLine();
|
||||
TableCellCenteredText(UIWidgets::ColorValues.at(UIWidgets::Colors::White),
|
||||
sceneObjectList[i].splitName.c_str());
|
||||
|
||||
SplitsPopImageButtonStyle();
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void DrawItemList(std::string tableName, IndexRangeObject range, uint32_t tableSize) {
|
||||
auto gui = std::dynamic_pointer_cast<Fast::Fast3dGui>(Ship::Context::GetRawInstance()->GetWindow()->GetGui());
|
||||
if (ImGui::BeginTable(tableName.c_str(), tableSize)) {
|
||||
for (uint32_t i = range.startIndex; i <= range.endIndex; i++) {
|
||||
const TimesplitObject& split = splitObjectList[i];
|
||||
ImGui::TableNextColumn();
|
||||
SplitsPushImageButtonStyle();
|
||||
if (ImGui::ImageButton(std::to_string(split.splitId).c_str(), gui->GetTextureByName(GetSplitImage(split)),
|
||||
GetSplitImageSize(split) * 1.5f, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0),
|
||||
GetSplitColor(split))) {
|
||||
if (split.splitType == SPLIT_TYPE_ITEM && itemSubMenuList.contains(split.splitId)) {
|
||||
shouldPopUpOpen = true;
|
||||
popupItem = split.splitId;
|
||||
ImGui::OpenPopup("ItemSubMenu");
|
||||
} else {
|
||||
AddSplitEntry(split.splitType, split.splitId);
|
||||
}
|
||||
}
|
||||
UIWidgets::Tooltip(splitObjectList[i].splitName.c_str());
|
||||
if (listName == "Bosses") {
|
||||
ImGui::SameLine();
|
||||
TableCellCenteredText(UIWidgets::ColorValues.at(UIWidgets::Colors::White),
|
||||
splitObjectList[i].splitName.c_str());
|
||||
}
|
||||
|
||||
SplitsPopImageButtonStyle();
|
||||
}
|
||||
HandlePopUpContext(popupItem);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void TimesplitsSettingsWindow::DrawElement() {
|
||||
auto gui = std::dynamic_pointer_cast<Fast::Fast3dGui>(Ship::Context::GetRawInstance()->GetWindow()->GetGui());
|
||||
bool shouldRemoveEntry = false;
|
||||
uint32_t entryIndex = 0;
|
||||
|
||||
UIWidgets::PushStyleTabs(THEME_COLOR);
|
||||
if (ImGui::BeginTabBar("Timesplit Settings Tabs")) {
|
||||
if (ImGui::BeginTabItem("List Options")) {
|
||||
DrawOptions();
|
||||
DrawActionButtons();
|
||||
ImGui::SeparatorText("Current Splits");
|
||||
DrawSplitsList(false);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Split Entries")) {
|
||||
if (ImGui::BeginTable("Split Settings", 3)) {
|
||||
ImGui::TableSetupColumn("Preview",
|
||||
ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 88.0f);
|
||||
ImGui::TableSetupColumn("Item Categories",
|
||||
ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderLabel, 120.0f);
|
||||
ImGui::TableSetupColumn("Item Grids",
|
||||
ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::BeginDisabled();
|
||||
UIWidgets::Button("Preview", {
|
||||
.color = THEME_COLOR,
|
||||
});
|
||||
ImGui::EndDisabled();
|
||||
ImGui::BeginChild("Preview List");
|
||||
for (size_t i = 0; i < splitList.size(); i++) {
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ((ImGui::GetContentRegionAvail().x - 50.0f) * 0.5f));
|
||||
ImGui::PushID((uint32_t)i);
|
||||
SplitsPushImageButtonStyle();
|
||||
if (ImGui::ImageButton(std::to_string(i).c_str(),
|
||||
gui->GetTextureByName(GetSplitImage(splitList[i])),
|
||||
GetSplitImageSize(splitList[i]), ImVec2(0, 0), ImVec2(1, 1),
|
||||
ImVec4(0, 0, 0, 0), GetSplitColor(splitList[i]))) {
|
||||
shouldRemoveEntry = true;
|
||||
entryIndex = (uint32_t)i;
|
||||
};
|
||||
UIWidgets::Tooltip(splitList[i].splitName.c_str());
|
||||
|
||||
HandleDragAndDrop(i);
|
||||
SplitsPopImageButtonStyle();
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (UIWidgets::Button("Equipment", {
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
range = GetIndexRange(SPLIT_TYPE_ITEM, ITEM_SWORD_KOKIRI, ITEM_DOUBLE_DEFENSE);
|
||||
listName = "Equipment";
|
||||
listColumns = 3;
|
||||
}
|
||||
if (UIWidgets::Button("Inventory", {
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
range = GetIndexRange(SPLIT_TYPE_ITEM, ITEM_STICK, ITEM_WEIRD_EGG);
|
||||
listName = "Inventory";
|
||||
listColumns = 6;
|
||||
}
|
||||
if (UIWidgets::Button("Quest", {
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
range = GetIndexRange(SPLIT_TYPE_ITEM, ITEM_SONG_LULLABY, ITEM_ZORA_SAPPHIRE);
|
||||
listName = "Quest";
|
||||
listColumns = 6;
|
||||
}
|
||||
if (UIWidgets::Button("Bosses", {
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
range = GetIndexRange(SPLIT_TYPE_BOSS, ACTOR_BOSS_GOMA, ACTOR_BOSS_GANON2);
|
||||
listName = "Bosses";
|
||||
listColumns = 2;
|
||||
}
|
||||
if (UIWidgets::Button("Entrances", {
|
||||
.color = THEME_COLOR,
|
||||
})) {
|
||||
listName = "Entrances";
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (listName != "Entrances") {
|
||||
DrawItemList(listName, range, listColumns);
|
||||
} else {
|
||||
DrawEntranceList();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
UIWidgets::PopStyleTabs();
|
||||
|
||||
if (shouldRemoveEntry) {
|
||||
RemoveSplitEntry(entryIndex);
|
||||
shouldRemoveEntry = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TimesplitsSettingsWindow::InitElement() {
|
||||
}
|
||||
|
||||
} // namespace TimeSplits
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#ifndef TIMESPLITS_SETTINGS_H
|
||||
#define TIMESPLITS_SETTINGS_H
|
||||
|
||||
#include <libultraship/libultraship.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace TimeSplits {
|
||||
|
||||
class TimesplitsSettingsWindow : public Ship::GuiWindow {
|
||||
public:
|
||||
using GuiWindow::GuiWindow;
|
||||
|
||||
void InitElement() override;
|
||||
void DrawElement() override;
|
||||
void UpdateElement() override{};
|
||||
};
|
||||
} // namespace TimeSplits
|
||||
#endif
|
||||
|
||||
#endif // TIMESPLITS_SETTINGS_H
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <random>
|
||||
#include "soh_assets.h"
|
||||
#include <libultraship/bridge/consolevariablebridge.h>
|
||||
#include "spdlog/common.h"
|
||||
|
||||
extern "C" {
|
||||
#include "z64.h"
|
||||
@@ -15,6 +16,15 @@ extern f32 sFontWidths[144];
|
||||
extern const char* fontTbl[140];
|
||||
}
|
||||
|
||||
extern std::string Ship_FormatTimeDisplay(uint32_t value) {
|
||||
uint32_t sec = value / 10;
|
||||
uint32_t hh = sec / 3600;
|
||||
uint32_t mm = (sec - hh * 3600) / 60;
|
||||
uint32_t ss = sec - hh * 3600 - mm * 60;
|
||||
uint32_t ds = value % 10;
|
||||
return spdlog::fmt_lib::format("{}:{:0>2}:{:0>2}.{}", hh, mm, ss, ds);
|
||||
}
|
||||
|
||||
constexpr f32 fourByThree = 4.0f / 3.0f;
|
||||
|
||||
// Gets the additional ratio of the screen compared to the original 4:3 ratio, clamping to 1 if smaller
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <string>
|
||||
std::string Ship_FormatTimeDisplay(uint32_t value);
|
||||
|
||||
void LoadGuiTextures();
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -272,4 +272,11 @@ void RegisterImGuiItemIcons() {
|
||||
for (const auto& entry : gSeedTextures) {
|
||||
gui->LoadGuiTexture(entry.tex, entry.tex, "", ImVec4(1, 1, 1, 1));
|
||||
}
|
||||
|
||||
gui->LoadGuiTexture("gPauseUnusedCursorTex", gPauseUnusedCursorTex, "", ImVec4(1, 1, 1, 1));
|
||||
|
||||
gui->LoadGuiTexture("gWTriforcePieceTex", gWTriforcePieceTex, "", ImVec4(1, 1, 1, 1));
|
||||
|
||||
// TODO: Figure out why this texture wouldn't load in the TimeSplits Windows
|
||||
gui->LoadGuiTexture("gSongNoteTex", gSongNoteTex, "", ImVec4(1, 1, 1, 1));
|
||||
}
|
||||
@@ -83,7 +83,8 @@ std::shared_ptr<HintTracker::HintTrackerSettingsWindow> mHintTrackerSettingsWind
|
||||
std::shared_ptr<HintTracker::HintTrackerWindow> mHintTrackerWindow;
|
||||
std::shared_ptr<ItemTrackerSettingsWindow> mItemTrackerSettingsWindow;
|
||||
std::shared_ptr<ItemTrackerWindow> mItemTrackerWindow;
|
||||
std::shared_ptr<TimeSplitWindow> mTimeSplitWindow;
|
||||
std::shared_ptr<TimeSplits::TimesplitsWindow> mTimeSplitsWindow;
|
||||
std::shared_ptr<TimeSplits::TimesplitsSettingsWindow> mTimeSplitSettingsWindow;
|
||||
std::shared_ptr<PlandomizerWindow> mPlandomizerWindow;
|
||||
std::shared_ptr<SohModalWindow> mModalWindow;
|
||||
std::shared_ptr<Notification::Window> mNotificationWindow;
|
||||
@@ -189,8 +190,13 @@ void SetupGuiElements() {
|
||||
mItemTrackerSettingsWindow = std::make_shared<ItemTrackerSettingsWindow>(CVAR_WINDOW("ItemTrackerSettings"),
|
||||
"Item Tracker Settings", ImVec2(733, 472));
|
||||
gui->AddGuiWindow(mItemTrackerSettingsWindow);
|
||||
mTimeSplitWindow = std::make_shared<TimeSplitWindow>(CVAR_WINDOW("TimeSplits"), "Time Splits", ImVec2(450, 660));
|
||||
gui->AddGuiWindow(mTimeSplitWindow);
|
||||
|
||||
mTimeSplitsWindow =
|
||||
std::make_shared<TimeSplits::TimesplitsWindow>(CVAR_WINDOW("TimeSplits"), "Time Splits", ImVec2(450, 660));
|
||||
gui->AddGuiWindow(mTimeSplitsWindow);
|
||||
mTimeSplitSettingsWindow = std::make_shared<TimeSplits::TimesplitsSettingsWindow>(
|
||||
CVAR_WINDOW("TimeSplitSettings"), "Time Splits Settings Window", ImVec2(450, 660));
|
||||
gui->AddGuiWindow(mTimeSplitSettingsWindow);
|
||||
mPlandomizerWindow =
|
||||
std::make_shared<PlandomizerWindow>(CVAR_WINDOW("PlandomizerEditor"), "Plandomizer Editor", ImVec2(850, 760));
|
||||
gui->AddGuiWindow(mPlandomizerWindow);
|
||||
@@ -233,7 +239,8 @@ void Destroy() {
|
||||
mGfxDebuggerWindow = nullptr;
|
||||
mInputViewer = nullptr;
|
||||
mInputViewerSettings = nullptr;
|
||||
mTimeSplitWindow = nullptr;
|
||||
mTimeSplitsWindow = nullptr;
|
||||
mTimeSplitSettingsWindow = nullptr;
|
||||
mPlandomizerWindow = nullptr;
|
||||
mTimeDisplayWindow = nullptr;
|
||||
mAnchorRoomWindow = nullptr;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "soh/Enhancements/randomizer/randomizer_hint_tracker.h"
|
||||
#include "soh/Enhancements/randomizer/randomizer_item_tracker.h"
|
||||
#include "soh/Enhancements/timesplits/TimeSplits.h"
|
||||
#include "soh/Enhancements/timesplits/TimeSplitsSettings.h"
|
||||
#include "soh/Enhancements/randomizer/Plandomizer.h"
|
||||
#include "SohModals.h"
|
||||
|
||||
|
||||
@@ -2176,12 +2176,9 @@ void SohMenu::AddMenuEnhancements() {
|
||||
// Time Splits
|
||||
path.sidebarName = "Time Splits";
|
||||
AddSidebarEntry("Enhancements", path.sidebarName, 1);
|
||||
AddWidget(path, "Popout Time Splits Window", WIDGET_WINDOW_BUTTON)
|
||||
.CVar(CVAR_WINDOW("TimeSplits"))
|
||||
.RaceDisable(false)
|
||||
.WindowName("Time Splits")
|
||||
.HideInSearch(true)
|
||||
.Options(WindowButtonOptions().Tooltip("Enables the separate Time Splits Window."));
|
||||
AddWidget(path, "Popout Timesplits Settings", WIDGET_WINDOW_BUTTON)
|
||||
.CVar("gWindows.Timesplits.Settings")
|
||||
.WindowName("Time Splits Settings Window");
|
||||
|
||||
// Timers
|
||||
path.sidebarName = "Timers";
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "soh/Enhancements/gameplaystats.h"
|
||||
#include "soh/Enhancements/custom-message/CustomMessageInterfaceAddon.h"
|
||||
#include "soh/Enhancements/cosmetics/cosmeticsTypes.h"
|
||||
#include "soh/Enhancements/enhancementTypes.h"
|
||||
#include "soh/ShipUtils.h"
|
||||
|
||||
#include <string.h>
|
||||
@@ -1839,7 +1838,9 @@ u8 Return_Item(u8 itemID, ModIndex modId, ItemID returnItem) {
|
||||
RandomizerGet randomizerGet = RetrieveRandomizerGetFromItemID(itemID);
|
||||
if (randomizerGet != RG_MAX) {
|
||||
// Vanilla ItemID with an associated RandomizerGet (These are items in extendedVanillaGetItemTable)
|
||||
return Return_Item_Entry(ItemTable_RetrieveEntry(MOD_RANDOMIZER, randomizerGet), returnItem);
|
||||
GetItemEntry itemEntry = ItemTable_RetrieveEntry(MOD_RANDOMIZER, randomizerGet);
|
||||
itemEntry.modIndex = modId;
|
||||
return Return_Item_Entry(itemEntry, returnItem);
|
||||
}
|
||||
|
||||
// All randomizer items should go through Randomizer_Item_Give, so this should never be reached
|
||||
|
||||
Reference in New Issue
Block a user