[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:
Caladius
2026-09-14 12:55:22 -04:00
committed by GitHub
parent 97fb13cbee
commit 43ea332874
14 changed files with 1557 additions and 1040 deletions
+13 -21
View File
@@ -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:
+12 -9
View File
@@ -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
+76 -34
View File
@@ -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
+10
View File
@@ -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
+3
View File
@@ -7,6 +7,9 @@
#include <set>
#include <vector>
#include <string>
std::string Ship_FormatTimeDisplay(uint32_t value);
void LoadGuiTextures();
extern "C" {
+7
View File
@@ -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));
}
+11 -4
View File
@@ -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;
+1
View File
@@ -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"
+3 -6
View File
@@ -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";
+3 -2
View File
@@ -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