mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-11-29 12:52:18 -05:00
Cleanup gameplay stats code/UI and support RTA timing (#2862)
This commit is contained in:
parent
f2f5a75cb0
commit
5de1240391
@ -83,6 +83,8 @@ typedef struct {
|
|||||||
/* */ u32 entrancesDiscovered[SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT];
|
/* */ u32 entrancesDiscovered[SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT];
|
||||||
/* */ u32 scenesDiscovered[SAVEFILE_SCENES_DISCOVERED_IDX_COUNT];
|
/* */ u32 scenesDiscovered[SAVEFILE_SCENES_DISCOVERED_IDX_COUNT];
|
||||||
/* */ u8 locationsSkipped[RC_MAX];
|
/* */ u8 locationsSkipped[RC_MAX];
|
||||||
|
/* */ bool rtaTiming;
|
||||||
|
/* */ uint64_t fileCreatedAt;
|
||||||
} SohStats;
|
} SohStats;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -14,120 +14,215 @@ extern "C" {
|
|||||||
#include <z64.h>
|
#include <z64.h>
|
||||||
#include "variables.h"
|
#include "variables.h"
|
||||||
extern PlayState* gPlayState;
|
extern PlayState* gPlayState;
|
||||||
|
uint64_t GetUnixTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::string> sceneMappings = {
|
const char* const sceneMappings[] = {
|
||||||
{"Inside the Deku Tree"},
|
"Inside the Deku Tree",
|
||||||
{"Dodongo's Cavern"},
|
"Dodongo's Cavern",
|
||||||
{"Inside Jabu-Jabu's Belly"},
|
"Inside Jabu-Jabu's Belly",
|
||||||
{"Forest Temple"},
|
"Forest Temple",
|
||||||
{"Fire Temple"},
|
"Fire Temple",
|
||||||
{"Water Temple"},
|
"Water Temple",
|
||||||
{"Spirit Temple"},
|
"Spirit Temple",
|
||||||
{"Shadow Temple"},
|
"Shadow Temple",
|
||||||
{"Bottom of the Well"},
|
"Bottom of the Well",
|
||||||
{"Ice Cavern"},
|
"Ice Cavern",
|
||||||
{"Ganon's Tower"},
|
"Ganon's Tower",
|
||||||
{"Gerudo Training Ground"},
|
"Gerudo Training Ground",
|
||||||
{"Theives' Hideout"},
|
"Theives' Hideout",
|
||||||
{"Inside Ganon's Castle"},
|
"Inside Ganon's Castle",
|
||||||
{"Tower Collapse"},
|
"Tower Collapse",
|
||||||
{"Castle Collapse"},
|
"Castle Collapse",
|
||||||
{"Treasure Box Shop"},
|
"Treasure Box Shop",
|
||||||
{"Gohma's Lair"},
|
"Gohma's Lair",
|
||||||
{"King Dodongo's Lair"},
|
"King Dodongo's Lair",
|
||||||
{"Barinade's Lair"},
|
"Barinade's Lair",
|
||||||
{"Phantom Ganon's Lair"},
|
"Phantom Ganon's Lair",
|
||||||
{"Volvagia's Lair"},
|
"Volvagia's Lair",
|
||||||
{"Morpha's Lair"},
|
"Morpha's Lair",
|
||||||
{"Twinrova's Lair"},
|
"Twinrova's Lair",
|
||||||
{"Bongo Bongo's Lair"},
|
"Bongo Bongo's Lair",
|
||||||
{"Ganondorf's Lair"},
|
"Ganondorf's Lair",
|
||||||
{"Ganon's Lair"},
|
"Ganon's Lair",
|
||||||
{"Market Entrance (Day)"},
|
"Market Entrance (Day)",
|
||||||
{"Market Entrance (Night)"},
|
"Market Entrance (Night)",
|
||||||
{"Market Entrance (Adult)"},
|
"Market Entrance (Adult)",
|
||||||
{"Back Alley (Day)"},
|
"Back Alley (Day)",
|
||||||
{"Back Alley (Night)"},
|
"Back Alley (Night)",
|
||||||
{"Market (Day)"},
|
"Market (Day)",
|
||||||
{"Market (Night)"},
|
"Market (Night)",
|
||||||
{"Market (Adult)"},
|
"Market (Adult)",
|
||||||
{"Outside ToT (Day)"},
|
"Outside ToT (Day)",
|
||||||
{"Outside ToT (Night)"},
|
"Outside ToT (Night)",
|
||||||
{"Outside ToT (Adult)"},
|
"Outside ToT (Adult)",
|
||||||
{"Know-It-All Bros' House"},
|
"Know-It-All Bros' House",
|
||||||
{"Twins' House"},
|
"Twins' House",
|
||||||
{"Mido's House"},
|
"Mido's House",
|
||||||
{"Saria's House"},
|
"Saria's House",
|
||||||
{"Carpenter Boss's House"},
|
"Carpenter Boss's House",
|
||||||
{"Man in Green's House"},
|
"Man in Green's House",
|
||||||
{"Bazaar"},
|
"Bazaar",
|
||||||
{"Kokiri Shop"},
|
"Kokiri Shop",
|
||||||
{"Goron Shop"},
|
"Goron Shop",
|
||||||
{"Zora Shop"},
|
"Zora Shop",
|
||||||
{"Kakariko Potion Shop"},
|
"Kakariko Potion Shop",
|
||||||
{"Market Potion Shop"},
|
"Market Potion Shop",
|
||||||
{"Bombchu Shop"},
|
"Bombchu Shop",
|
||||||
{"Happy Mask Shop"},
|
"Happy Mask Shop",
|
||||||
{"Link's House"},
|
"Link's House",
|
||||||
{"Richard's House"},
|
"Richard's House",
|
||||||
{"Stable"},
|
"Stable",
|
||||||
{"Impa's House"},
|
"Impa's House",
|
||||||
{"Lakeside Lab"},
|
"Lakeside Lab",
|
||||||
{"Carpenters' Tent"},
|
"Carpenters' Tent",
|
||||||
{"Gravekeeper's Hut"},
|
"Gravekeeper's Hut",
|
||||||
{"Great Fairy"},
|
"Great Fairy",
|
||||||
{"Fairy Fountain"},
|
"Fairy Fountain",
|
||||||
{"Great Fairy"},
|
"Great Fairy",
|
||||||
{"Grotto"},
|
"Grotto",
|
||||||
{"Redead Grave"},
|
"Redead Grave",
|
||||||
{"Fairy Fountain Grave"},
|
"Fairy Fountain Grave",
|
||||||
{"Royal Family's Tomb"},
|
"Royal Family's Tomb",
|
||||||
{"Shooting Gallery"},
|
"Shooting Gallery",
|
||||||
{"Temple of Time"},
|
"Temple of Time",
|
||||||
{"Chamber of Sages"},
|
"Chamber of Sages",
|
||||||
{"Castle Maze (Day)"},
|
"Castle Maze (Day)",
|
||||||
{"Castle Maze (Night)"},
|
"Castle Maze (Night)",
|
||||||
{"Cutscene Map"},
|
"Cutscene Map",
|
||||||
{"Dampe's Grave"},
|
"Dampe's Grave",
|
||||||
{"Fishing Pond"},
|
"Fishing Pond",
|
||||||
{"Castle Courtyard"},
|
"Castle Courtyard",
|
||||||
{"Bombchu Bowling Alley"},
|
"Bombchu Bowling Alley",
|
||||||
{"Ranch House"},
|
"Ranch House",
|
||||||
{"Guard House"},
|
"Guard House",
|
||||||
{"Granny's Potion Shop"},
|
"Granny's Potion Shop",
|
||||||
{"Ganon Fight"},
|
"Ganon Fight",
|
||||||
{"House of Skulltula"},
|
"House of Skulltula",
|
||||||
{"Hyrule Field"},
|
"Hyrule Field",
|
||||||
{"Kakariko Village"},
|
"Kakariko Village",
|
||||||
{"Graveyard"},
|
"Graveyard",
|
||||||
{"Zora's River"},
|
"Zora's River",
|
||||||
{"Kokiri Forest"},
|
"Kokiri Forest",
|
||||||
{"Sacred Forest Meadow"},
|
"Sacred Forest Meadow",
|
||||||
{"Lake Hylia"},
|
"Lake Hylia",
|
||||||
{"Zora's Domain"},
|
"Zora's Domain",
|
||||||
{"Zora's Fountain"},
|
"Zora's Fountain",
|
||||||
{"Gerudo Valley"},
|
"Gerudo Valley",
|
||||||
{"Lost Woods"},
|
"Lost Woods",
|
||||||
{"Desert Colossus"},
|
"Desert Colossus",
|
||||||
{"Gerudo's Fortress"},
|
"Gerudo's Fortress",
|
||||||
{"Haunted Wasteland"},
|
"Haunted Wasteland",
|
||||||
{"Hyrule Castle"},
|
"Hyrule Castle",
|
||||||
{"Death Mountain Trail"},
|
"Death Mountain Trail",
|
||||||
{"Death Mountain Crater"},
|
"Death Mountain Crater",
|
||||||
{"Goron City"},
|
"Goron City",
|
||||||
{"Lon Lon Ranch"},
|
"Lon Lon Ranch",
|
||||||
{"Outside Ganon's Castle"},
|
"Outside Ganon's Castle",
|
||||||
//Debug Rooms
|
//Debug Rooms
|
||||||
{"Test Map"},
|
"Test Map",
|
||||||
{"Test Room"},
|
"Test Room",
|
||||||
{"Depth Test"},
|
"Depth Test",
|
||||||
{"Stalfos Mini-Boss"},
|
"Stalfos Mini-Boss",
|
||||||
{"Stalfos Boss"},
|
"Stalfos Boss",
|
||||||
{"Dark Link"},
|
"Dark Link",
|
||||||
{"Castle Maze (Broken)"},
|
"Castle Maze (Broken)",
|
||||||
{"SRD Room"},
|
"SRD Room",
|
||||||
{"Chest Room"}
|
"Chest Room",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* const countMappings[] = {
|
||||||
|
"Anubis:",
|
||||||
|
"Armos:",
|
||||||
|
"Arwing:",
|
||||||
|
"Bari:",
|
||||||
|
"Biri:",
|
||||||
|
"Beamos:",
|
||||||
|
"Big Octo:",
|
||||||
|
"Bubble (Blue):",
|
||||||
|
"Bubble (Green):",
|
||||||
|
"Bubble (Red):",
|
||||||
|
"Bubble (White):",
|
||||||
|
"Business Scrub:",
|
||||||
|
"Dark Link:",
|
||||||
|
"Dead Hand:",
|
||||||
|
"Deku Baba:",
|
||||||
|
"Deku Baba (Big):",
|
||||||
|
"Deku Scrub:",
|
||||||
|
"Dinolfos:",
|
||||||
|
"Dodongo:",
|
||||||
|
"Dodongo (Baby):",
|
||||||
|
"Door Mimic:",
|
||||||
|
"Flare Dancer:",
|
||||||
|
"Floormaster:",
|
||||||
|
"Flying Floor Tile:",
|
||||||
|
"Flying Pot:",
|
||||||
|
"Freezard:",
|
||||||
|
"Gerudo Thief:",
|
||||||
|
"Gibdo:",
|
||||||
|
"Gohma Larva:",
|
||||||
|
"Guay:",
|
||||||
|
"Iron Knuckle:",
|
||||||
|
"Iron Knuckle (Nab):",
|
||||||
|
"Keese:",
|
||||||
|
"Keese (Fire):",
|
||||||
|
"Keese (Ice):",
|
||||||
|
"Leever:",
|
||||||
|
"Leever (Big):",
|
||||||
|
"Like-Like:",
|
||||||
|
"Lizalfos:",
|
||||||
|
"Mad Scrub:",
|
||||||
|
"Moblin:",
|
||||||
|
"Moblin (Club):",
|
||||||
|
"Octorok:",
|
||||||
|
"Parasitic Tentacle:",
|
||||||
|
"Peahat:",
|
||||||
|
"Peahat Larva:",
|
||||||
|
"Poe:",
|
||||||
|
"Poe (Big):",
|
||||||
|
"Poe (Composer):",
|
||||||
|
"Poe Sisters:",
|
||||||
|
"Redead:",
|
||||||
|
"Shabom:",
|
||||||
|
"Shellblade:",
|
||||||
|
"Skull Kid:",
|
||||||
|
"Skulltula:",
|
||||||
|
"Skulltula (Big):",
|
||||||
|
"Skulltula (Gold):",
|
||||||
|
"Skullwalltula:",
|
||||||
|
"Spike:",
|
||||||
|
"Stalchild:",
|
||||||
|
"Stalfos:",
|
||||||
|
"Stinger:",
|
||||||
|
"Tailpasaran:",
|
||||||
|
"Tektite (Blue):",
|
||||||
|
"Tektite (Red):",
|
||||||
|
"Torch Slug:",
|
||||||
|
"Wallmaster:",
|
||||||
|
"Withered Deku Baba:",
|
||||||
|
"Wolfos:",
|
||||||
|
"Wolfos (White):",
|
||||||
|
"Deku Sticks:",
|
||||||
|
"Deku Nuts:",
|
||||||
|
"Bombs:",
|
||||||
|
"Arrows:",
|
||||||
|
"Deku Seeds:",
|
||||||
|
"Bombchus:",
|
||||||
|
"Beans:",
|
||||||
|
"A:",
|
||||||
|
"B:",
|
||||||
|
"L:",
|
||||||
|
"R:",
|
||||||
|
"Z:",
|
||||||
|
"C-Up:",
|
||||||
|
"C-Right:",
|
||||||
|
"C-Down:",
|
||||||
|
"C-Left:",
|
||||||
|
"D-Up:",
|
||||||
|
"D-Right:",
|
||||||
|
"D-Down:",
|
||||||
|
"D-Left:",
|
||||||
|
"Start:",
|
||||||
};
|
};
|
||||||
|
|
||||||
#define COLOR_WHITE ImVec4(1.00f, 1.00f, 1.00f, 1.00f)
|
#define COLOR_WHITE ImVec4(1.00f, 1.00f, 1.00f, 1.00f)
|
||||||
@ -157,125 +252,134 @@ TimestampInfo itemTimestampDisplay[TIMESTAMP_MAX];
|
|||||||
TimestampInfo sceneTimestampDisplay[8191];
|
TimestampInfo sceneTimestampDisplay[8191];
|
||||||
//std::vector<TimestampInfo> sceneTimestampDisplay;
|
//std::vector<TimestampInfo> sceneTimestampDisplay;
|
||||||
|
|
||||||
void DisplayTimeHHMMSS(uint32_t timeInTenthsOfSeconds, std::string text, ImVec4 color) {
|
std::string formatTimestampGameplayStat(uint32_t value) {
|
||||||
|
uint32_t sec = value / 10;
|
||||||
uint32_t sec = timeInTenthsOfSeconds / 10;
|
|
||||||
uint32_t hh = sec / 3600;
|
uint32_t hh = sec / 3600;
|
||||||
uint32_t mm = (sec - hh * 3600) / 60;
|
uint32_t mm = (sec - hh * 3600) / 60;
|
||||||
uint32_t ss = sec - hh * 3600 - mm * 60;
|
uint32_t ss = sec - hh * 3600 - mm * 60;
|
||||||
uint32_t ds = timeInTenthsOfSeconds % 10;
|
uint32_t ds = value % 10;
|
||||||
|
return fmt::format("{}:{:0>2}:{:0>2}.{}", hh, mm, ss, ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string formatIntGameplayStat(uint32_t value) {
|
||||||
|
return fmt::format("{}", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string formatHexGameplayStat(uint32_t value) {
|
||||||
|
return fmt::format("{:#x} ({:d})", value, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string formatHexOnlyGameplayStat(uint32_t value) {
|
||||||
|
return fmt::format("{:#x}", value, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameplayStatsRow(const char* label, std::string value, ImVec4 color = COLOR_WHITE) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||||
|
ImGui::TableNextRow();
|
||||||
std::string padded = fmt::format("{:<40}", text);
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text(padded.c_str());
|
ImGui::Text(label);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize(value.c_str()).x - 8.0f));
|
||||||
ImGui::Text("%2u:%02u:%02u.%u", hh, mm, ss, ds);
|
ImGui::Text("%s", value.c_str());
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SortChronological(TimestampInfo* arr, size_t len) {
|
bool compareTimestampInfoByTime(const TimestampInfo& a, const TimestampInfo& b) {
|
||||||
TimestampInfo temp;
|
return CVarGetInteger("gGameplayStats.TimestampsReverse", 0) ? a.time > b.time : a.time < b.time;
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
for (int j = 0; j + 1 < len - i; j++) {
|
|
||||||
if (arr[j].time > arr[j + 1].time) {
|
|
||||||
temp = arr[j];
|
|
||||||
arr[j] = arr[j + 1];
|
|
||||||
arr[j + 1] = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayStat(const char* text, uint32_t value) {
|
const char* ResolveSceneID(int sceneID, int roomID){
|
||||||
|
|
||||||
ImGui::Text(text);
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Text("%7u", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayStatIfNonZero(const char* text, uint32_t value) {
|
|
||||||
if (value > 0) {
|
|
||||||
DisplayStat(text, value);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ResolveSceneID(int sceneID, int roomID){
|
|
||||||
std::string scene = "";
|
|
||||||
if (sceneID == SCENE_KAKUSIANA) {
|
if (sceneID == SCENE_KAKUSIANA) {
|
||||||
switch (roomID) {
|
switch (roomID) {
|
||||||
case 0:
|
case 0:
|
||||||
scene = "Generic Grotto";
|
return "Generic Grotto";
|
||||||
break;
|
|
||||||
case 1:
|
case 1:
|
||||||
scene = "Lake Hylia Scrub Grotto";
|
return "Lake Hylia Scrub Grotto";
|
||||||
break;
|
|
||||||
case 2:
|
case 2:
|
||||||
scene = "Redead Grotto";
|
return "Redead Grotto";
|
||||||
break;
|
|
||||||
case 3:
|
case 3:
|
||||||
scene = "Cow Grotto";
|
return "Cow Grotto";
|
||||||
break;
|
|
||||||
case 4:
|
case 4:
|
||||||
scene = "Scrub Trio";
|
return "Scrub Trio";
|
||||||
break;
|
|
||||||
case 5:
|
case 5:
|
||||||
scene = "Flooded Grotto";
|
return "Flooded Grotto";
|
||||||
break;
|
|
||||||
case 6:
|
case 6:
|
||||||
scene = "Scrub Duo (Upgrade)";
|
return "Scrub Duo (Upgrade)";
|
||||||
break;
|
|
||||||
case 7:
|
case 7:
|
||||||
scene = "Wolfos Grotto";
|
return "Wolfos Grotto";
|
||||||
break;
|
|
||||||
case 8:
|
case 8:
|
||||||
scene = "Hyrule Castle Storms Grotto";
|
return "Hyrule Castle Storms Grotto";
|
||||||
break;
|
|
||||||
case 9:
|
case 9:
|
||||||
scene = "Scrub Duo";
|
return "Scrub Duo";
|
||||||
break;
|
|
||||||
case 10:
|
case 10:
|
||||||
scene = "Tektite Grotto";
|
return "Tektite Grotto";
|
||||||
break;
|
|
||||||
case 11:
|
case 11:
|
||||||
scene = "Forest Stage";
|
return "Forest Stage";
|
||||||
break;
|
|
||||||
case 12:
|
case 12:
|
||||||
scene = "Webbed Grotto";
|
return "Webbed Grotto";
|
||||||
break;
|
|
||||||
case 13:
|
case 13:
|
||||||
scene = "Big Skulltula Grotto";
|
return "Big Skulltula Grotto";
|
||||||
break;
|
|
||||||
default:
|
|
||||||
scene = "???";
|
|
||||||
};
|
};
|
||||||
} else if (sceneID == SCENE_HAKASITARELAY) {
|
} else if (sceneID == SCENE_HAKASITARELAY) {
|
||||||
//Only the last room of Dampe's Grave (rm 6) is considered the windmill
|
//Only the last room of Dampe's Grave (rm 6) is considered the windmill
|
||||||
scene = roomID == 6 ? "Windmill" : "Dampe's Grave";
|
return roomID == 6 ? "Windmill" : "Dampe's Grave";
|
||||||
} else if (sceneID < SCENE_ID_MAX) {
|
} else if (sceneID < SCENE_ID_MAX) {
|
||||||
scene = sceneMappings[sceneID];
|
return sceneMappings[sceneID];
|
||||||
} else {
|
|
||||||
scene = "???";
|
|
||||||
}
|
}
|
||||||
return scene;
|
|
||||||
|
return "???";
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawStatsTracker(bool& open) {
|
void DrawGameplayStatsHeader() {
|
||||||
if (!open) {
|
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 4.0f, 4.0f });
|
||||||
if (CVarGetInteger("gGameplayStatsEnabled", 0)) {
|
ImGui::BeginTable("gameplayStatsHeader", 1, ImGuiTableFlags_BordersOuter);
|
||||||
CVarClear("gGameplayStatsEnabled");
|
ImGui::TableSetupColumn("stat", ImGuiTableColumnFlags_WidthStretch);
|
||||||
LUS::RequestCvarSaveOnNextTick();
|
GameplayStatsRow("Build Version:", gSaveContext.sohStats.buildVersion);
|
||||||
}
|
if (gSaveContext.sohStats.rtaTiming) {
|
||||||
return;
|
GameplayStatsRow("Total Time (RTA):", formatTimestampGameplayStat(GAMEPLAYSTAT_TOTAL_TIME), gSaveContext.sohStats.gameComplete ? COLOR_GREEN : COLOR_WHITE);
|
||||||
|
} else {
|
||||||
|
GameplayStatsRow("Total Game Time:", formatTimestampGameplayStat(GAMEPLAYSTAT_TOTAL_TIME), gSaveContext.sohStats.gameComplete ? COLOR_GREEN : COLOR_WHITE);
|
||||||
|
}
|
||||||
|
if (CVarGetInteger("gGameplayStats.ShowAdditionalTimers", 0)) { // !Only display total game time
|
||||||
|
GameplayStatsRow("Gameplay Time:", formatTimestampGameplayStat(gSaveContext.sohStats.playTimer / 2), COLOR_GREY);
|
||||||
|
GameplayStatsRow("Pause Menu Time:", formatTimestampGameplayStat(gSaveContext.sohStats.pauseTimer / 3), COLOR_GREY);
|
||||||
|
GameplayStatsRow("Time in scene:", formatTimestampGameplayStat(gSaveContext.sohStats.sceneTimer / 2), COLOR_LIGHT_BLUE);
|
||||||
|
GameplayStatsRow("Time in room:", formatTimestampGameplayStat(gSaveContext.sohStats.roomTimer / 2), COLOR_LIGHT_BLUE);
|
||||||
|
}
|
||||||
|
if (gPlayState != NULL && CVarGetInteger("gGameplayStats.ShowDebugInfo", 0)) { // && display debug info
|
||||||
|
GameplayStatsRow("play->sceneNum:", formatHexGameplayStat(gPlayState->sceneNum), COLOR_YELLOW);
|
||||||
|
GameplayStatsRow("gSaveContext.entranceIndex:", formatHexGameplayStat(gSaveContext.entranceIndex), COLOR_YELLOW);
|
||||||
|
GameplayStatsRow("gSaveContext.cutsceneIndex:", formatHexOnlyGameplayStat(gSaveContext.cutsceneIndex), COLOR_YELLOW);
|
||||||
|
GameplayStatsRow("play->roomCtx.curRoom.num:", formatIntGameplayStat(gPlayState->roomCtx.curRoom.num), COLOR_YELLOW);
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawGameplayStatsTimestampsTab() {
|
||||||
|
// Set up the array of item timestamps and then sort it chronologically
|
||||||
|
for (int i = 0; i < TIMESTAMP_MAX; i++) {
|
||||||
|
strcpy(itemTimestampDisplay[i].name, itemTimestampDisplayName[i]);
|
||||||
|
itemTimestampDisplay[i].time = gSaveContext.sohStats.itemTimestamp[i];
|
||||||
|
itemTimestampDisplay[i].color = itemTimestampDisplayColor[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_Appearing);
|
std::sort(itemTimestampDisplay, itemTimestampDisplay + TIMESTAMP_MAX, compareTimestampInfoByTime);
|
||||||
if (!ImGui::Begin("Gameplay Stats", &open, ImGuiWindowFlags_NoFocusOnAppearing)) {
|
|
||||||
ImGui::End();
|
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 4.0f, 4.0f });
|
||||||
return;
|
ImGui::BeginTable("gameplayStatsTimestamps", 1, ImGuiTableFlags_BordersOuter);
|
||||||
|
ImGui::TableSetupColumn("stat", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
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, 21) > 1) {
|
||||||
|
GameplayStatsRow(itemTimestampDisplay[i].name, formatTimestampGameplayStat(itemTimestampDisplay[i].time), itemTimestampDisplay[i].color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
u32 totalTimer = GAMEPLAYSTAT_TOTAL_TIME;
|
ImGui::EndTable();
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawGameplayStatsCountsTab() {
|
||||||
u32 enemiesDefeated = 0;
|
u32 enemiesDefeated = 0;
|
||||||
u32 ammoUsed = 0;
|
u32 ammoUsed = 0;
|
||||||
u32 buttonPresses = 0;
|
u32 buttonPresses = 0;
|
||||||
@ -297,230 +401,154 @@ void DrawStatsTracker(bool& open) {
|
|||||||
for (int i = COUNT_BUTTON_PRESSES_A; i <= COUNT_BUTTON_PRESSES_START; i++) {
|
for (int i = COUNT_BUTTON_PRESSES_A; i <= COUNT_BUTTON_PRESSES_START; i++) {
|
||||||
buttonPresses += gSaveContext.sohStats.count[i];
|
buttonPresses += gSaveContext.sohStats.count[i];
|
||||||
}
|
}
|
||||||
// Set up the array of item timestamps and then sort it chronologically
|
|
||||||
for (int i = 0; i < TIMESTAMP_MAX; i++) {
|
|
||||||
strcpy(itemTimestampDisplay[i].name, itemTimestampDisplayName[i]);
|
|
||||||
itemTimestampDisplay[i].time = gSaveContext.sohStats.itemTimestamp[i];
|
|
||||||
itemTimestampDisplay[i].color = itemTimestampDisplayColor[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 4.0f, 4.0f });
|
||||||
|
ImGui::BeginTable("gameplayStatsCounts", 1, ImGuiTableFlags_BordersOuter);
|
||||||
|
ImGui::TableSetupColumn("stat", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
GameplayStatsRow("Enemies Defeated:", formatIntGameplayStat(enemiesDefeated));
|
||||||
|
if (enemiesDefeated > 0) {
|
||||||
|
ImGui::TableNextRow(); ImGui::TableNextColumn();
|
||||||
|
if (ImGui::TreeNodeEx("Enemy Details...", ImGuiTreeNodeFlags_NoTreePushOnOpen)) {
|
||||||
|
for (int i = COUNT_ENEMIES_DEFEATED_ANUBIS; i <= COUNT_ENEMIES_DEFEATED_WOLFOS; i++) {
|
||||||
|
if (i == COUNT_ENEMIES_DEFEATED_FLOORMASTER) {
|
||||||
|
GameplayStatsRow(countMappings[i], formatIntGameplayStat(gSaveContext.sohStats.count[i] / 3));
|
||||||
|
} else {
|
||||||
|
GameplayStatsRow(countMappings[i], formatIntGameplayStat(gSaveContext.sohStats.count[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GameplayStatsRow("Rupees Collected:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_RUPEES_COLLECTED]));
|
||||||
|
UIWidgets::Tooltip("Includes rupees collected with a full wallet.");
|
||||||
|
GameplayStatsRow("Rupees Spent:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_RUPEES_SPENT]));
|
||||||
|
GameplayStatsRow("Chests Opened:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_CHESTS_OPENED]));
|
||||||
|
GameplayStatsRow("Ammo Used:", formatIntGameplayStat(ammoUsed));
|
||||||
|
if (ammoUsed > 0) {
|
||||||
|
ImGui::TableNextRow(); ImGui::TableNextColumn();
|
||||||
|
if (ImGui::TreeNodeEx("Ammo Details...", ImGuiTreeNodeFlags_NoTreePushOnOpen)) {
|
||||||
|
for (int i = COUNT_AMMO_USED_STICK; i <= COUNT_AMMO_USED_BEAN; i++) {
|
||||||
|
GameplayStatsRow(countMappings[i], formatIntGameplayStat(gSaveContext.sohStats.count[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GameplayStatsRow("Damage Taken:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_DAMAGE_TAKEN]));
|
||||||
|
GameplayStatsRow("Sword Swings:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_SWORD_SWINGS]));
|
||||||
|
GameplayStatsRow("Steps Taken:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_STEPS]));
|
||||||
|
// If using MM Bunny Hood enhancement, show how long it's been equipped (not counting pause time)
|
||||||
|
if (CVarGetInteger("gMMBunnyHood", 0) || gSaveContext.sohStats.count[COUNT_TIME_BUNNY_HOOD] > 0) {
|
||||||
|
GameplayStatsRow("Bunny Hood Time:", formatTimestampGameplayStat(gSaveContext.sohStats.count[COUNT_TIME_BUNNY_HOOD] / 2));
|
||||||
|
}
|
||||||
|
GameplayStatsRow("Rolls:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_ROLLS]));
|
||||||
|
GameplayStatsRow("Bonks:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_BONKS]));
|
||||||
|
GameplayStatsRow("Sidehops:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_SIDEHOPS]));
|
||||||
|
GameplayStatsRow("Backflips:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_BACKFLIPS]));
|
||||||
|
GameplayStatsRow("Ice Traps:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_ICE_TRAPS]));
|
||||||
|
GameplayStatsRow("Pauses:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_PAUSES]));
|
||||||
|
GameplayStatsRow("Pots Smashed:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_POTS_BROKEN]));
|
||||||
|
GameplayStatsRow("Bushes Cut:", formatIntGameplayStat(gSaveContext.sohStats.count[COUNT_BUSHES_CUT]));
|
||||||
|
GameplayStatsRow("Buttons Pressed:", formatIntGameplayStat(buttonPresses));
|
||||||
|
if (buttonPresses > 0) {
|
||||||
|
ImGui::TableNextRow(); ImGui::TableNextColumn();
|
||||||
|
if (ImGui::TreeNodeEx("Buttons...", ImGuiTreeNodeFlags_NoTreePushOnOpen)) {
|
||||||
|
for (int i = COUNT_BUTTON_PRESSES_A; i <= COUNT_BUTTON_PRESSES_START; i++) {
|
||||||
|
GameplayStatsRow(countMappings[i], formatIntGameplayStat(gSaveContext.sohStats.count[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawGameplayStatsBreakdownTab() {
|
||||||
for (int i = 0; i < gSaveContext.sohStats.tsIdx; i++) {
|
for (int i = 0; i < gSaveContext.sohStats.tsIdx; i++) {
|
||||||
std::string sceneName = ResolveSceneID(gSaveContext.sohStats.sceneTimestamps[i].scene, gSaveContext.sohStats.sceneTimestamps[i].room);
|
std::string sceneName = ResolveSceneID(gSaveContext.sohStats.sceneTimestamps[i].scene, gSaveContext.sohStats.sceneTimestamps[i].room);
|
||||||
std::string name;
|
std::string name;
|
||||||
if (CVarGetInteger("gGameplayStatRoomBreakdown", 0) && gSaveContext.sohStats.sceneTimestamps[i].scene != SCENE_KAKUSIANA) {
|
if (CVarGetInteger("gGameplayStats.RoomBreakdown", 0) && gSaveContext.sohStats.sceneTimestamps[i].scene != SCENE_KAKUSIANA) {
|
||||||
name = fmt::format("{:s} Room {:d}", sceneName, gSaveContext.sohStats.sceneTimestamps[i].room);
|
name = fmt::format("{:s} Room {:d}", sceneName, gSaveContext.sohStats.sceneTimestamps[i].room);
|
||||||
} else {
|
} else {
|
||||||
name = sceneName;
|
name = sceneName;
|
||||||
}
|
}
|
||||||
strcpy(sceneTimestampDisplay[i].name, name.c_str());
|
strcpy(sceneTimestampDisplay[i].name, name.c_str());
|
||||||
sceneTimestampDisplay[i].time = CVarGetInteger("gGameplayStatRoomBreakdown", 0) ?
|
sceneTimestampDisplay[i].time = CVarGetInteger("gGameplayStats.RoomBreakdown", 0) ?
|
||||||
gSaveContext.sohStats.sceneTimestamps[i].roomTime : gSaveContext.sohStats.sceneTimestamps[i].sceneTime;
|
gSaveContext.sohStats.sceneTimestamps[i].roomTime : gSaveContext.sohStats.sceneTimestamps[i].sceneTime;
|
||||||
sceneTimestampDisplay[i].color = COLOR_GREY;
|
sceneTimestampDisplay[i].color = COLOR_GREY;
|
||||||
sceneTimestampDisplay[i].isRoom = gSaveContext.sohStats.sceneTimestamps[i].isRoom;
|
sceneTimestampDisplay[i].isRoom = gSaveContext.sohStats.sceneTimestamps[i].isRoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
SortChronological(itemTimestampDisplay, sizeof(itemTimestampDisplay) / sizeof(itemTimestampDisplay[0]));
|
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 4.0f, 4.0f });
|
||||||
|
ImGui::BeginTable("gameplayStatsCounts", 1, ImGuiTableFlags_BordersOuter);
|
||||||
|
ImGui::TableSetupColumn("stat", ImGuiTableColumnFlags_WidthStretch);
|
||||||
// Begin drawing the table and showing the stats
|
for (int i = 0; i < gSaveContext.sohStats.tsIdx; i++) {
|
||||||
|
TimestampInfo tsInfo = sceneTimestampDisplay[i];
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f });
|
bool canShow = !tsInfo.isRoom || CVarGetInteger("gGameplayStats.RoomBreakdown", 0);
|
||||||
ImGui::BeginTable("timers", 1, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV);
|
if (tsInfo.time > 0 && strnlen(tsInfo.name, 40) > 1 && canShow) {
|
||||||
ImGui::TableSetupColumn("Timers", ImGuiTableColumnFlags_WidthStretch, 200.0f);
|
GameplayStatsRow(tsInfo.name, formatTimestampGameplayStat(tsInfo.time), tsInfo.color);
|
||||||
ImGui::TableNextColumn();
|
}
|
||||||
|
}
|
||||||
DisplayTimeHHMMSS(totalTimer, "Total Game Time: ", COLOR_WHITE);
|
std::string toPass;
|
||||||
UIWidgets::Tooltip("Timer accuracy may be affected by game performance and loading.");
|
if (CVarGetInteger("gGameplayStats.RoomBreakdown", 0) && gSaveContext.sohStats.sceneNum != SCENE_KAKUSIANA) {
|
||||||
DisplayTimeHHMMSS(gSaveContext.sohStats.playTimer / 2, "Gameplay Time: ", COLOR_WHITE);
|
toPass = fmt::format("{:s} Room {:d}", ResolveSceneID(gSaveContext.sohStats.sceneNum, gSaveContext.sohStats.roomNum), gSaveContext.sohStats.roomNum);
|
||||||
UIWidgets::Tooltip("Timer accuracy may be affected by game performance and loading.");
|
} else {
|
||||||
DisplayTimeHHMMSS(gSaveContext.sohStats.pauseTimer / 3, "Pause Menu Time: ", COLOR_WHITE);
|
toPass = ResolveSceneID(gSaveContext.sohStats.sceneNum, gSaveContext.sohStats.roomNum);
|
||||||
DisplayTimeHHMMSS(gSaveContext.sohStats.sceneTimer / 2, "Time in scene: ", COLOR_LIGHT_BLUE);
|
}
|
||||||
UIWidgets::Tooltip("Timer accuracy may be affected by game performance and loading.");
|
GameplayStatsRow(toPass.c_str(), formatTimestampGameplayStat(CURRENT_MODE_TIMER / 2));
|
||||||
DisplayTimeHHMMSS(gSaveContext.sohStats.roomTimer / 2, "Time in room: ", COLOR_LIGHT_BLUE);
|
|
||||||
UIWidgets::Tooltip("Timer accuracy may be affected by game performance and loading.");
|
|
||||||
ImGui::Text("Current room: %d", gSaveContext.sohStats.roomNum);
|
|
||||||
|
|
||||||
ImGui::PopStyleVar(1);
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawGameplayStatsOptionsTab() {
|
||||||
|
UIWidgets::PaddedEnhancementCheckbox("Show latest timestamps on top", "gGameplayStats.TimestampsReverse");
|
||||||
|
UIWidgets::PaddedEnhancementCheckbox("Room Breakdown", "gGameplayStats.RoomBreakdown");
|
||||||
|
ImGui::SameLine();
|
||||||
|
UIWidgets::InsertHelpHoverText("Allows a more in-depth perspective of time spent in a certain map.");
|
||||||
|
UIWidgets::PaddedEnhancementCheckbox("RTA Timing on new files", "gGameplayStats.RTATiming");
|
||||||
|
ImGui::SameLine();
|
||||||
|
UIWidgets::InsertHelpHoverText(
|
||||||
|
"Timestamps are relative to starting timestamp rather than in game time, usually necessary for races/speedruns.\n\n"
|
||||||
|
"Starting timestamp is on first non-c-up input after intro cutscene.\n\n"
|
||||||
|
"NOTE: THIS NEEDS TO BE SET BEFORE CREATING A FILE TO TAKE EFFECT"
|
||||||
|
);
|
||||||
|
UIWidgets::PaddedEnhancementCheckbox("Show additional detail timers", "gGameplayStats.ShowAdditionalTimers");
|
||||||
|
UIWidgets::PaddedEnhancementCheckbox("Show Debug Info", "gGameplayStats.ShowDebugInfo");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawStatsTracker(bool& open) {
|
||||||
|
if (!open) {
|
||||||
|
if (CVarGetInteger("gGameplayStatsEnabled", 0)) {
|
||||||
|
CVarClear("gGameplayStatsEnabled");
|
||||||
|
LUS::RequestCvarSaveOnNextTick();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(480, 550), ImGuiCond_Appearing);
|
||||||
|
if (!ImGui::Begin("Gameplay Stats", &open, ImGuiWindowFlags_NoFocusOnAppearing)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawGameplayStatsHeader();
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 8.0f, 8.0f });
|
|
||||||
if (ImGui::BeginTabBar("Stats", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) {
|
if (ImGui::BeginTabBar("Stats", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) {
|
||||||
if (ImGui::BeginTabItem("Timestamps")) {
|
if (ImGui::BeginTabItem("Timestamps")) {
|
||||||
// Display chronological timestamps of items obtained and bosses defeated
|
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, 21) > 1) {
|
|
||||||
DisplayTimeHHMMSS(itemTimestampDisplay[i].time, itemTimestampDisplay[i].name, itemTimestampDisplay[i].color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
if (ImGui::BeginTabItem("Counts")) {
|
if (ImGui::BeginTabItem("Counts")) {
|
||||||
DisplayStat("Enemies Defeated: ", enemiesDefeated);
|
DrawGameplayStatsCountsTab();
|
||||||
// Show breakdown of enemies defeated in a tree. Only show counts for enemies if they've been defeated at least once.
|
|
||||||
if (enemiesDefeated > 0) {
|
|
||||||
if (ImGui::TreeNode("Enemy Details...")) {
|
|
||||||
DisplayStatIfNonZero("Anubis: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ANUBIS]);
|
|
||||||
DisplayStatIfNonZero("Armos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ARMOS]);
|
|
||||||
DisplayStatIfNonZero("Arwing: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_ARWING]);
|
|
||||||
DisplayStatIfNonZero("Bari: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BARI]);
|
|
||||||
DisplayStatIfNonZero("Biri: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BIRI]);
|
|
||||||
DisplayStatIfNonZero("Beamos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BEAMOS]);
|
|
||||||
DisplayStatIfNonZero("Big Octo: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BIG_OCTO]);
|
|
||||||
DisplayStatIfNonZero("Bubble (Blue): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_BLUE]);
|
|
||||||
DisplayStatIfNonZero("Bubble (Green): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_GREEN]);
|
|
||||||
DisplayStatIfNonZero("Bubble (Red): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_RED]);
|
|
||||||
DisplayStatIfNonZero("Bubble (White): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUBBLE_WHITE]);
|
|
||||||
DisplayStatIfNonZero("Business Scrub: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_BUSINESS_SCRUB]);
|
|
||||||
DisplayStatIfNonZero("Dark Link: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DARK_LINK]);
|
|
||||||
DisplayStatIfNonZero("Dead Hand: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEAD_HAND]);
|
|
||||||
DisplayStatIfNonZero("Deku Baba: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA]);
|
|
||||||
DisplayStatIfNonZero("Deku Baba (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_BABA_BIG]);
|
|
||||||
DisplayStatIfNonZero("Deku Scrub: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DEKU_SCRUB]);
|
|
||||||
DisplayStatIfNonZero("Dinolfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DINOLFOS]);
|
|
||||||
DisplayStatIfNonZero("Dodongo: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DODONGO]);
|
|
||||||
DisplayStatIfNonZero("Dodongo (Baby): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DODONGO_BABY]);
|
|
||||||
DisplayStatIfNonZero("Door Mimic: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_DOOR_TRAP]);
|
|
||||||
DisplayStatIfNonZero("Flare Dancer: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLARE_DANCER]);
|
|
||||||
DisplayStatIfNonZero("Floormaster: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLOORMASTER]/3);
|
|
||||||
DisplayStatIfNonZero("Flying Floor Tile: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLOOR_TILE]);
|
|
||||||
DisplayStatIfNonZero("Flying Pot: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FLYING_POT]);
|
|
||||||
DisplayStatIfNonZero("Freezard: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_FREEZARD]);
|
|
||||||
DisplayStatIfNonZero("Gerudo Thief: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GERUDO_THIEF]);
|
|
||||||
DisplayStatIfNonZero("Gibdo: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GIBDO]);
|
|
||||||
DisplayStatIfNonZero("Gohma Larva: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GOHMA_LARVA]);
|
|
||||||
DisplayStatIfNonZero("Guay: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_GUAY]);
|
|
||||||
DisplayStatIfNonZero("Iron Knuckle: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE]);
|
|
||||||
DisplayStatIfNonZero("Iron Knuckle (Nab): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_IRON_KNUCKLE_NABOORU]);
|
|
||||||
DisplayStatIfNonZero("Keese: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE]);
|
|
||||||
DisplayStatIfNonZero("Keese (Fire): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE_FIRE]);
|
|
||||||
DisplayStatIfNonZero("Keese (Ice): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_KEESE_ICE]);
|
|
||||||
DisplayStatIfNonZero("Leever: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER]);
|
|
||||||
DisplayStatIfNonZero("Leever (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LEEVER_BIG]);
|
|
||||||
DisplayStatIfNonZero("Like-Like: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LIKE_LIKE]);
|
|
||||||
DisplayStatIfNonZero("Lizalfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_LIZALFOS]);
|
|
||||||
DisplayStatIfNonZero("Mad Scrub: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MAD_SCRUB]);
|
|
||||||
DisplayStatIfNonZero("Moblin: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MOBLIN]);
|
|
||||||
DisplayStatIfNonZero("Moblin (Club): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_MOBLIN_CLUB]);
|
|
||||||
DisplayStatIfNonZero("Octorok: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_OCTOROK]);
|
|
||||||
DisplayStatIfNonZero("Parasitic Tentacle: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PARASITIC_TENTACLE]);
|
|
||||||
DisplayStatIfNonZero("Peahat: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT]);
|
|
||||||
DisplayStatIfNonZero("Peahat Larva: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_PEAHAT_LARVA]);
|
|
||||||
DisplayStatIfNonZero("Poe: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE]);
|
|
||||||
DisplayStatIfNonZero("Poe (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_BIG]);
|
|
||||||
DisplayStatIfNonZero("Poe (Composer): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_COMPOSER]);
|
|
||||||
DisplayStatIfNonZero("Poe Sisters: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_POE_SISTERS]);
|
|
||||||
DisplayStatIfNonZero("Redead: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_REDEAD]);
|
|
||||||
DisplayStatIfNonZero("Shabom: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SHABOM]);
|
|
||||||
DisplayStatIfNonZero("Shellblade: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SHELLBLADE]);
|
|
||||||
DisplayStatIfNonZero("Skull Kid: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULL_KID]);
|
|
||||||
DisplayStatIfNonZero("Skulltula: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA]);
|
|
||||||
DisplayStatIfNonZero("Skulltula (Big): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_BIG]);
|
|
||||||
DisplayStatIfNonZero("Skulltula (Gold): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLTULA_GOLD]);
|
|
||||||
DisplayStatIfNonZero("Skullwalltula: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SKULLWALLTULA]);
|
|
||||||
DisplayStatIfNonZero("Spike: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_SPIKE]);
|
|
||||||
DisplayStatIfNonZero("Stalchild: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STALCHILD]);
|
|
||||||
DisplayStatIfNonZero("Stalfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STALFOS]);
|
|
||||||
DisplayStatIfNonZero("Stinger: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_STINGER]);
|
|
||||||
DisplayStatIfNonZero("Tailpasaran: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TAILPASARAN]);
|
|
||||||
DisplayStatIfNonZero("Tektite (Blue): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_BLUE]);
|
|
||||||
DisplayStatIfNonZero("Tektite (Red): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_RED]);
|
|
||||||
DisplayStatIfNonZero("Torch Slug: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TORCH_SLUG]);
|
|
||||||
DisplayStatIfNonZero("Wallmaster: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WALLMASTER]);
|
|
||||||
DisplayStatIfNonZero("Withered Deku Baba: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WITHERED_DEKU_BABA]);
|
|
||||||
DisplayStatIfNonZero("Wolfos: ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WOLFOS]);
|
|
||||||
DisplayStatIfNonZero("Wolfos (White): ", gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_WOLFOS_WHITE]);
|
|
||||||
ImGui::NewLine();
|
|
||||||
ImGui::TreePop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayStat("Rupees Collected: ", gSaveContext.sohStats.count[COUNT_RUPEES_COLLECTED]);
|
|
||||||
UIWidgets::Tooltip("Includes rupees collected with a full wallet.");
|
|
||||||
DisplayStat("Rupees Spent: ", gSaveContext.sohStats.count[COUNT_RUPEES_SPENT]);
|
|
||||||
DisplayStat("Chests Opened: ", gSaveContext.sohStats.count[COUNT_CHESTS_OPENED]);
|
|
||||||
|
|
||||||
DisplayStat("Ammo Used: ", ammoUsed);
|
|
||||||
// Show breakdown of ammo used in a collapsible tree. Only show ammo types if they've been used at least once.
|
|
||||||
if (ammoUsed > 0) {
|
|
||||||
if (ImGui::TreeNode("Ammo Details...")) {
|
|
||||||
DisplayStatIfNonZero("Deku Sticks: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_STICK]);
|
|
||||||
DisplayStatIfNonZero("Deku Nuts: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_NUT]);
|
|
||||||
DisplayStatIfNonZero("Deku Seeds: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_SEED]);
|
|
||||||
DisplayStatIfNonZero("Bombs: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_BOMB]);
|
|
||||||
DisplayStatIfNonZero("Bombchus: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_BOMBCHU]);
|
|
||||||
DisplayStatIfNonZero("Arrows: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_ARROW]);
|
|
||||||
DisplayStatIfNonZero("Beans: ", gSaveContext.sohStats.count[COUNT_AMMO_USED_BEAN]);
|
|
||||||
ImGui::NewLine();
|
|
||||||
ImGui::TreePop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DisplayStat("Damage Taken: ", gSaveContext.sohStats.count[COUNT_DAMAGE_TAKEN]);
|
|
||||||
DisplayStat("Sword Swings: ", gSaveContext.sohStats.count[COUNT_SWORD_SWINGS]);
|
|
||||||
DisplayStat("Steps Taken: ", gSaveContext.sohStats.count[COUNT_STEPS]);
|
|
||||||
// If using MM Bunny Hood enhancement, show how long it's been equipped (not counting pause time)
|
|
||||||
if (CVarGetInteger("gMMBunnyHood", 0) || gSaveContext.sohStats.count[COUNT_TIME_BUNNY_HOOD] > 0) {
|
|
||||||
DisplayTimeHHMMSS(gSaveContext.sohStats.count[COUNT_TIME_BUNNY_HOOD] / 2, "Bunny Hood Time: ", COLOR_WHITE);
|
|
||||||
}
|
|
||||||
DisplayStat("Rolls: ", gSaveContext.sohStats.count[COUNT_ROLLS]);
|
|
||||||
DisplayStat("Bonks: ", gSaveContext.sohStats.count[COUNT_BONKS]);
|
|
||||||
DisplayStat("Sidehops: ", gSaveContext.sohStats.count[COUNT_SIDEHOPS]);
|
|
||||||
DisplayStat("Backflips: ", gSaveContext.sohStats.count[COUNT_BACKFLIPS]);
|
|
||||||
DisplayStat("Ice Traps: ", gSaveContext.sohStats.count[COUNT_ICE_TRAPS]);
|
|
||||||
DisplayStat("Pauses: ", gSaveContext.sohStats.count[COUNT_PAUSES]);
|
|
||||||
DisplayStat("Pots Smashed: ", gSaveContext.sohStats.count[COUNT_POTS_BROKEN]);
|
|
||||||
DisplayStat("Bushes Cut: ", gSaveContext.sohStats.count[COUNT_BUSHES_CUT]);
|
|
||||||
DisplayStat("Buttons Pressed: ", buttonPresses);
|
|
||||||
// Show breakdown of ammo used in a collapsible tree. Only show ammo types if they've been used at least once.
|
|
||||||
if (buttonPresses > 0) {
|
|
||||||
if (ImGui::TreeNode("Buttons...")) {
|
|
||||||
DisplayStatIfNonZero("A: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_A]);
|
|
||||||
DisplayStatIfNonZero("B: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_B]);
|
|
||||||
DisplayStatIfNonZero("L: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_L]);
|
|
||||||
DisplayStatIfNonZero("R: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_R]);
|
|
||||||
DisplayStatIfNonZero("Z: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_Z]);
|
|
||||||
DisplayStatIfNonZero("C-Up: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CUP]);
|
|
||||||
DisplayStatIfNonZero("C-Right: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CRIGHT]);
|
|
||||||
DisplayStatIfNonZero("C-Down: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CDOWN]);
|
|
||||||
DisplayStatIfNonZero("C-Left: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_CLEFT]);
|
|
||||||
DisplayStatIfNonZero("D-Up: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DUP]);
|
|
||||||
DisplayStatIfNonZero("D-Right: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DRIGHT]);
|
|
||||||
DisplayStatIfNonZero("D-Down: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DDOWN]);
|
|
||||||
DisplayStatIfNonZero("D-Left: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_DLEFT]);
|
|
||||||
DisplayStatIfNonZero("Start: ", gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_START]);
|
|
||||||
ImGui::NewLine();
|
|
||||||
ImGui::TreePop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
if (ImGui::BeginTabItem("Breakdown")) {
|
|
||||||
UIWidgets::PaddedEnhancementCheckbox("Room Breakdown", "gGameplayStatRoomBreakdown");
|
|
||||||
ImGui::SameLine();
|
|
||||||
UIWidgets::InsertHelpHoverText("Allows a more in-depth perspective of time spent in a certain map.");
|
|
||||||
if (gPlayState == NULL) {
|
|
||||||
ImGui::Text("Waiting for file load...");
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < gSaveContext.sohStats.tsIdx; i++) {
|
|
||||||
TimestampInfo tsInfo = sceneTimestampDisplay[i];
|
|
||||||
bool canShow = !tsInfo.isRoom || CVarGetInteger("gGameplayStatRoomBreakdown", 0);
|
|
||||||
if (tsInfo.time > 0 && strnlen(tsInfo.name, 40) > 1 && canShow) {
|
|
||||||
DisplayTimeHHMMSS(tsInfo.time, tsInfo.name, tsInfo.color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::string toPass;
|
|
||||||
if (CVarGetInteger("gGameplayStatRoomBreakdown", 0) && gSaveContext.sohStats.sceneNum != SCENE_KAKUSIANA) {
|
|
||||||
toPass = fmt::format("{:s} Room {:d}", ResolveSceneID(gSaveContext.sohStats.sceneNum, gSaveContext.sohStats.roomNum), gSaveContext.sohStats.roomNum);
|
|
||||||
} else {
|
|
||||||
toPass = ResolveSceneID(gSaveContext.sohStats.sceneNum, gSaveContext.sohStats.roomNum);
|
|
||||||
}
|
|
||||||
DisplayTimeHHMMSS(CURRENT_MODE_TIMER / 2, toPass.c_str(), COLOR_WHITE);
|
|
||||||
}
|
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
ImGui::EndTabBar();
|
if (ImGui::BeginTabItem("Breakdown")) {
|
||||||
|
DrawGameplayStatsBreakdownTab();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginTabItem("Options")) {
|
||||||
|
DrawGameplayStatsOptionsTab();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar(1);
|
|
||||||
ImGui::Text("Note: Gameplay stats are saved to the current file and will be\nlost if you quit without saving.");
|
ImGui::Text("Note: Gameplay stats are saved to the current file and will be\nlost if you quit without saving.");
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
@ -670,7 +698,7 @@ void SetupDisplayColors() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void InitStatTracker() {
|
extern "C" void InitStatTracker() {
|
||||||
LUS::AddWindow("Enhancements", "Gameplay Stats", DrawStatsTracker, CVarGetInteger("gGameplayStatsEnabled", 0));
|
LUS::AddWindow("Enhancements", "Gameplay Stats", DrawStatsTracker, CVarGetInteger("gGameplayStats.Enabled", 0));
|
||||||
SetupDisplayNames();
|
SetupDisplayNames();
|
||||||
SetupDisplayColors();
|
SetupDisplayColors();
|
||||||
}
|
}
|
@ -1,10 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// Total gameplay time is tracked in tenths of seconds
|
// When using RTA timing
|
||||||
// I.E. game time counts frames at 20fps/2, pause time counts frames at 30fps/3
|
// get the diff since the save was created,
|
||||||
// Frame counts in z_play.c and z_kaleido_scope_call.c
|
// unless the game is complete in which we use the defeated ganon timestamp
|
||||||
#define GAMEPLAYSTAT_TOTAL_TIME (gSaveContext.sohStats.playTimer / 2 + gSaveContext.sohStats.pauseTimer / 3)
|
// When not using RTA timing
|
||||||
#define CURRENT_MODE_TIMER (CVarGetInteger("gGameplayStatRoomBreakdown", 0) ?\
|
// 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.sohStats.rtaTiming ?\
|
||||||
|
(!gSaveContext.sohStats.gameComplete ?\
|
||||||
|
(!gSaveContext.sohStats.fileCreatedAt ? 0 : ((GetUnixTimestamp() - gSaveContext.sohStats.fileCreatedAt) / 100)) :\
|
||||||
|
(gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANON])) :\
|
||||||
|
(gSaveContext.sohStats.playTimer / 2 + gSaveContext.sohStats.pauseTimer / 3))
|
||||||
|
#define CURRENT_MODE_TIMER (CVarGetInteger("gGameplayStats.RoomBreakdown", 0) ?\
|
||||||
gSaveContext.sohStats.roomTimer :\
|
gSaveContext.sohStats.roomTimer :\
|
||||||
gSaveContext.sohStats.sceneTimer)
|
gSaveContext.sohStats.sceneTimer)
|
||||||
|
|
||||||
|
@ -1013,14 +1013,14 @@ namespace GameMenuBar {
|
|||||||
LUS::RequestCvarSaveOnNextTick();
|
LUS::RequestCvarSaveOnNextTick();
|
||||||
LUS::EnableWindow("Audio Editor", CVarGetInteger("gAudioEditor.WindowOpen", 0));
|
LUS::EnableWindow("Audio Editor", CVarGetInteger("gAudioEditor.WindowOpen", 0));
|
||||||
}
|
}
|
||||||
if (ImGui::Button(GetWindowButtonText("Gameplay Stats", CVarGetInteger("gGameplayStatsEnabled", 0)).c_str(), ImVec2(-1.0f, 0.0f))) {
|
if (ImGui::Button(GetWindowButtonText("Gameplay Stats", CVarGetInteger("gGameplayStats.Enabled", 0)).c_str(), ImVec2(-1.0f, 0.0f))) {
|
||||||
if (CVarGetInteger("gGameplayStatsEnabled", 0)) {
|
if (CVarGetInteger("gGameplayStats.Enabled", 0)) {
|
||||||
CVarClear("gGameplayStatsEnabled");
|
CVarClear("gGameplayStats.Enabled");
|
||||||
} else {
|
} else {
|
||||||
CVarSetInteger("gGameplayStatsEnabled", 1);
|
CVarSetInteger("gGameplayStats.Enabled", 1);
|
||||||
}
|
}
|
||||||
LUS::RequestCvarSaveOnNextTick();
|
LUS::RequestCvarSaveOnNextTick();
|
||||||
LUS::EnableWindow("Gameplay Stats", CVarGetInteger("gGameplayStatsEnabled", 0));
|
LUS::EnableWindow("Gameplay Stats", CVarGetInteger("gGameplayStats.Enabled", 0));
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar(3);
|
ImGui::PopStyleVar(3);
|
||||||
ImGui::PopStyleColor(1);
|
ImGui::PopStyleColor(1);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include <ResourceManager.h>
|
#include <ResourceManager.h>
|
||||||
#include <File.h>
|
#include <File.h>
|
||||||
@ -831,6 +832,14 @@ extern "C" uint64_t GetPerfCounter() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
extern "C" uint64_t GetUnixTimestamp() {
|
||||||
|
auto time = std::chrono::system_clock::now();
|
||||||
|
auto since_epoch = time.time_since_epoch();
|
||||||
|
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(since_epoch);
|
||||||
|
long now = millis.count();
|
||||||
|
return now;
|
||||||
|
}
|
||||||
|
|
||||||
// C->C++ Bridge
|
// C->C++ Bridge
|
||||||
extern "C" void Graph_ProcessFrame(void (*run_one_game_iter)(void)) {
|
extern "C" void Graph_ProcessFrame(void (*run_one_game_iter)(void)) {
|
||||||
OTRGlobals::Instance->context->GetWindow()->MainLoop(run_one_game_iter);
|
OTRGlobals::Instance->context->GetWindow()->MainLoop(run_one_game_iter);
|
||||||
|
@ -88,6 +88,7 @@ void Ctx_ReadSaveFile(uintptr_t addr, void* dramAddr, size_t size);
|
|||||||
void Ctx_WriteSaveFile(uintptr_t addr, void* dramAddr, size_t size);
|
void Ctx_WriteSaveFile(uintptr_t addr, void* dramAddr, size_t size);
|
||||||
|
|
||||||
uint64_t GetPerfCounter();
|
uint64_t GetPerfCounter();
|
||||||
|
uint64_t GetUnixTimestamp();
|
||||||
struct SkeletonHeader* ResourceMgr_LoadSkeletonByName(const char* path, SkelAnime* skelAnime);
|
struct SkeletonHeader* ResourceMgr_LoadSkeletonByName(const char* path, SkelAnime* skelAnime);
|
||||||
void ResourceMgr_UnregisterSkeleton(SkelAnime* skelAnime);
|
void ResourceMgr_UnregisterSkeleton(SkelAnime* skelAnime);
|
||||||
void ResourceMgr_ClearSkeletons();
|
void ResourceMgr_ClearSkeletons();
|
||||||
|
@ -512,6 +512,8 @@ void SaveManager::InitFileNormal() {
|
|||||||
for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys); dungeon++) {
|
for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys); dungeon++) {
|
||||||
gSaveContext.sohStats.dungeonKeys[dungeon] = 0;
|
gSaveContext.sohStats.dungeonKeys[dungeon] = 0;
|
||||||
}
|
}
|
||||||
|
gSaveContext.sohStats.rtaTiming = CVarGetInteger("gGameplayStats.RTATiming", 0);
|
||||||
|
gSaveContext.sohStats.fileCreatedAt = 0;
|
||||||
gSaveContext.sohStats.playTimer = 0;
|
gSaveContext.sohStats.playTimer = 0;
|
||||||
gSaveContext.sohStats.pauseTimer = 0;
|
gSaveContext.sohStats.pauseTimer = 0;
|
||||||
for (int timestamp = 0; timestamp < ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp); timestamp++) {
|
for (int timestamp = 0; timestamp < ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp); timestamp++) {
|
||||||
@ -1112,6 +1114,8 @@ void SaveManager::LoadBaseVersion2() {
|
|||||||
SaveManager::Instance->LoadArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) {
|
SaveManager::Instance->LoadArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) {
|
||||||
SaveManager::Instance->LoadData("", gSaveContext.sohStats.dungeonKeys[i]);
|
SaveManager::Instance->LoadData("", gSaveContext.sohStats.dungeonKeys[i]);
|
||||||
});
|
});
|
||||||
|
SaveManager::Instance->LoadData("rtaTiming", gSaveContext.sohStats.rtaTiming);
|
||||||
|
SaveManager::Instance->LoadData("fileCreatedAt", gSaveContext.sohStats.fileCreatedAt);
|
||||||
SaveManager::Instance->LoadData("playTimer", gSaveContext.sohStats.playTimer);
|
SaveManager::Instance->LoadData("playTimer", gSaveContext.sohStats.playTimer);
|
||||||
SaveManager::Instance->LoadData("pauseTimer", gSaveContext.sohStats.pauseTimer);
|
SaveManager::Instance->LoadData("pauseTimer", gSaveContext.sohStats.pauseTimer);
|
||||||
SaveManager::Instance->LoadArray("timestamps", ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp), [](size_t i) {
|
SaveManager::Instance->LoadArray("timestamps", ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp), [](size_t i) {
|
||||||
@ -1326,6 +1330,8 @@ void SaveManager::LoadBaseVersion3() {
|
|||||||
SaveManager::Instance->LoadArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) {
|
SaveManager::Instance->LoadArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) {
|
||||||
SaveManager::Instance->LoadData("", gSaveContext.sohStats.dungeonKeys[i]);
|
SaveManager::Instance->LoadData("", gSaveContext.sohStats.dungeonKeys[i]);
|
||||||
});
|
});
|
||||||
|
SaveManager::Instance->LoadData("rtaTiming", gSaveContext.sohStats.rtaTiming);
|
||||||
|
SaveManager::Instance->LoadData("fileCreatedAt", gSaveContext.sohStats.fileCreatedAt);
|
||||||
SaveManager::Instance->LoadData("playTimer", gSaveContext.sohStats.playTimer);
|
SaveManager::Instance->LoadData("playTimer", gSaveContext.sohStats.playTimer);
|
||||||
SaveManager::Instance->LoadData("pauseTimer", gSaveContext.sohStats.pauseTimer);
|
SaveManager::Instance->LoadData("pauseTimer", gSaveContext.sohStats.pauseTimer);
|
||||||
SaveManager::Instance->LoadArray("itemTimestamps", ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp), [](size_t i) {
|
SaveManager::Instance->LoadArray("itemTimestamps", ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp), [](size_t i) {
|
||||||
@ -1535,6 +1541,8 @@ void SaveManager::SaveBase(SaveContext* saveContext) {
|
|||||||
SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(saveContext->sohStats.dungeonKeys), [&](size_t i) {
|
SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(saveContext->sohStats.dungeonKeys), [&](size_t i) {
|
||||||
SaveManager::Instance->SaveData("", saveContext->sohStats.dungeonKeys[i]);
|
SaveManager::Instance->SaveData("", saveContext->sohStats.dungeonKeys[i]);
|
||||||
});
|
});
|
||||||
|
SaveManager::Instance->SaveData("rtaTiming", saveContext->sohStats.rtaTiming);
|
||||||
|
SaveManager::Instance->SaveData("fileCreatedAt", saveContext->sohStats.fileCreatedAt);
|
||||||
SaveManager::Instance->SaveData("playTimer", saveContext->sohStats.playTimer);
|
SaveManager::Instance->SaveData("playTimer", saveContext->sohStats.playTimer);
|
||||||
SaveManager::Instance->SaveData("pauseTimer", saveContext->sohStats.pauseTimer);
|
SaveManager::Instance->SaveData("pauseTimer", saveContext->sohStats.pauseTimer);
|
||||||
SaveManager::Instance->SaveArray("itemTimestamps", ARRAY_COUNT(saveContext->sohStats.itemTimestamp), [&](size_t i) {
|
SaveManager::Instance->SaveArray("itemTimestamps", ARRAY_COUNT(saveContext->sohStats.itemTimestamp), [&](size_t i) {
|
||||||
|
@ -751,6 +751,14 @@ void Play_Update(PlayState* play) {
|
|||||||
if (CHECK_BTN_ALL(input[0].press.button, BTN_R)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_R]++;}
|
if (CHECK_BTN_ALL(input[0].press.button, BTN_R)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_R]++;}
|
||||||
if (CHECK_BTN_ALL(input[0].press.button, BTN_Z)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_Z]++;}
|
if (CHECK_BTN_ALL(input[0].press.button, BTN_Z)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_Z]++;}
|
||||||
if (CHECK_BTN_ALL(input[0].press.button, BTN_START)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_START]++;}
|
if (CHECK_BTN_ALL(input[0].press.button, BTN_START)) {gSaveContext.sohStats.count[COUNT_BUTTON_PRESSES_START]++;}
|
||||||
|
|
||||||
|
// Start RTA timing on first non-c-up input after intro cutscene
|
||||||
|
if (
|
||||||
|
!gSaveContext.sohStats.fileCreatedAt && !Player_InCsMode(play) &&
|
||||||
|
((input[0].press.button && input[0].press.button != 0x8) || input[0].rel.stick_x != 0 || input[0].rel.stick_y != 0)
|
||||||
|
) {
|
||||||
|
gSaveContext.sohStats.fileCreatedAt = GetUnixTimestamp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gTrnsnUnkState != 0) {
|
if (gTrnsnUnkState != 0) {
|
||||||
|
@ -1680,8 +1680,8 @@ void func_8090120C(BossGanon2* this, PlayState* play) {
|
|||||||
if ((ABS(temp_a0_2) < 0x2000) && (sqrtf(SQ(temp_f14) + SQ(temp_f12)) < 70.0f) &&
|
if ((ABS(temp_a0_2) < 0x2000) && (sqrtf(SQ(temp_f14) + SQ(temp_f12)) < 70.0f) &&
|
||||||
(player->swordState != 0) && (player->heldItemAction == PLAYER_IA_SWORD_MASTER)) {
|
(player->swordState != 0) && (player->heldItemAction == PLAYER_IA_SWORD_MASTER)) {
|
||||||
func_80064520(play, &play->csCtx);
|
func_80064520(play, &play->csCtx);
|
||||||
gSaveContext.sohStats.gameComplete = true;
|
|
||||||
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
|
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
|
||||||
|
gSaveContext.sohStats.gameComplete = true;
|
||||||
this->unk_39E = Play_CreateSubCamera(play);
|
this->unk_39E = Play_CreateSubCamera(play);
|
||||||
Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_WAIT);
|
Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_WAIT);
|
||||||
Play_ChangeCameraStatus(play, this->unk_39E, CAM_STAT_ACTIVE);
|
Play_ChangeCameraStatus(play, this->unk_39E, CAM_STAT_ACTIVE);
|
||||||
|
Loading…
Reference in New Issue
Block a user