[Feature] In-game gameplay stats timer (#2910)

* Implement in-game gameplay stats timer

* Change timer to render on top of everything
This commit is contained in:
aMannus 2023-05-31 00:57:45 +02:00 committed by GitHub
parent e8eaac4d77
commit b25e4d4f26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 134 additions and 5 deletions

View File

@ -1089,6 +1089,7 @@ void func_80088AA0(s16 seconds);
void func_80088AF0(PlayState* play);
void func_80088B34(s16 arg0);
void Interface_Draw(PlayState* play);
void Interface_DrawTotalGameplayTimer(PlayState* play);
void Interface_Update(PlayState* play);
Path* Path_GetByIndex(PlayState* play, s16 index, s16 max);
f32 Path_OrientAndGetDistSq(Actor* actor, Path* path, s16 waypoint, s16* yaw);

View File

@ -321,7 +321,7 @@ static std::map<std::string, CosmeticOption> cosmeticOptions = {
static const char* MarginCvarList[] {
"gHearts", "gHeartsCount", "gMagicBar", "gVSOA", "gBBtn", "gABtn", "gStartBtn",
"gCBtnU", "gCBtnD", "gCBtnL", "gCBtnR", "gDPad", "gMinimap",
"gSKC", "gRC", "gCarrots", "gTimers", "gAS", "gTCM", "gTCB"
"gSKC", "gRC", "gCarrots", "gTimers", "gAS", "gTCM", "gTCB", "gIGT"
};
static const char* MarginCvarNonAnchor[]{ "gCarrots", "gTimers", "gAS", "gTCM","gTCB" };
@ -1406,6 +1406,19 @@ void Draw_Placements(){
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("In-game Gameplay Timer position")) {
if (ImGui::BeginTable("tablegameplaytimer", 1, FlagsTable)) {
ImGui::TableSetupColumn("In-game Gameplay Timer settings", FlagsCell, TablesCellsWidth);
Table_InitHeader(false);
DrawUseMarginsSlider("In-game Gameplay Timer", "gIGT");
DrawPositionsRadioBoxes("gIGT");
DrawPositionSlider("gIGT", 0, ImGui::GetWindowViewport()->Size.y / 2, -50,
ImGui::GetWindowViewport()->Size.x / 2 + 10);
DrawScaleSlider("gIGT", 1.0f);
ImGui::NewLine();
ImGui::EndTable();
}
}
}
void DrawSillyTab() {

View File

@ -276,6 +276,14 @@ std::string formatHexOnlyGameplayStat(uint32_t value) {
return fmt::format("{:#x}", value, value);
}
extern "C" char* GameplayStats_GetCurrentTime() {
std::string timeString = formatTimestampGameplayStat(GAMEPLAYSTAT_TOTAL_TIME).c_str();
const int stringLength = timeString.length();
char* timeChar = new char[stringLength + 1];
strcpy(timeChar, timeString.c_str());
return timeChar;
}
void LoadStatsVersion1() {
std::string buildVersion;
SaveManager::Instance->LoadData("buildVersion", buildVersion);
@ -598,18 +606,20 @@ void DrawGameplayStatsBreakdownTab() {
}
void DrawGameplayStatsOptionsTab() {
UIWidgets::PaddedEnhancementCheckbox("Show latest timestamps on top", "gGameplayStats.TimestampsReverse");
UIWidgets::PaddedEnhancementCheckbox("Room Breakdown", "gGameplayStats.RoomBreakdown");
UIWidgets::PaddedEnhancementCheckbox("Show in-game total timer", "gGameplayStats.ShowIngameTimer", true, false);
UIWidgets::InsertHelpHoverText("Keep track of the timer as an in-game HUD element. The position of the timer can be changed in the Cosmetics Editor.");
UIWidgets::PaddedEnhancementCheckbox("Show latest timestamps on top", "gGameplayStats.TimestampsReverse", true, false);
UIWidgets::PaddedEnhancementCheckbox("Room Breakdown", "gGameplayStats.RoomBreakdown", true, false);
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");
UIWidgets::PaddedEnhancementCheckbox("RTA Timing on new files", "gGameplayStats.RTATiming", true, false);
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 additional detail timers", "gGameplayStats.ShowAdditionalTimers", true, false);
UIWidgets::PaddedEnhancementCheckbox("Show Debug Info", "gGameplayStats.ShowDebugInfo");
}

View File

@ -17,6 +17,7 @@
gSaveContext.sohStats.sceneTimer)
void InitStatTracker();
char* GameplayStats_GetCurrentTime();
typedef enum {
// 0x00 to 0x9B (0 to 155) used for getting items,

View File

@ -7,6 +7,7 @@
#include "soh/Enhancements/randomizer/adult_trade_shuffle.h"
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
#include "libultraship/bridge.h"
#include "soh/Enhancements/gameplaystats.h"
#ifdef _MSC_VER
#include <stdlib.h>
@ -5979,6 +5980,107 @@ void Interface_Draw(PlayState* play) {
CLOSE_DISPS(play->state.gfxCtx);
}
void Interface_DrawTotalGameplayTimer(PlayState* play) {
// Draw timer based on the Gameplay Stats total time.
if (CVarGetInteger("gGameplayStats.ShowIngameTimer", 0) && gSaveContext.fileNum >= 0 && gSaveContext.fileNum <= 2) {
s32 X_Margins_Timer = 0;
if (CVarGetInteger("gIGTUseMargins", 0) != 0) {
if (CVarGetInteger("gIGTPosType", 0) == 0) {
X_Margins_Timer = Left_HUD_Margin;
};
}
s32 rectLeftOri = OTRGetRectDimensionFromLeftEdge(24 + X_Margins_Timer);
s32 rectTopOri = 73;
if (CVarGetInteger("gIGTPosType", 0) != 0) {
rectTopOri = (CVarGetInteger("gIGTPosY", 0));
if (CVarGetInteger("gIGTPosType", 0) == 1) { // Anchor Left
if (CVarGetInteger("gIGTUseMargins", 0) != 0) {
X_Margins_Timer = Left_HUD_Margin;
};
rectLeftOri = OTRGetRectDimensionFromLeftEdge(CVarGetInteger("gIGTPosX", 0) + X_Margins_Timer);
} else if (CVarGetInteger("gIGTPosType", 0) == 2) { // Anchor Right
if (CVarGetInteger("gIGTUseMargins", 0) != 0) {
X_Margins_Timer = Right_HUD_Margin;
};
rectLeftOri = OTRGetRectDimensionFromRightEdge(CVarGetInteger("gIGTPosX", 0) + X_Margins_Timer);
} else if (CVarGetInteger("gIGTPosType", 0) == 3) { // Anchor None
rectLeftOri = CVarGetInteger("gIGTPosX", 0) + 204 + X_Margins_Timer;
} else if (CVarGetInteger("gIGTPosType", 0) == 4) { // Hidden
rectLeftOri = -9999;
}
}
s32 rectLeft;
s32 rectTop;
s32 rectWidth = 8;
s32 rectHeightOri = 16;
s32 rectHeight;
OPEN_DISPS(play->state.gfxCtx);
gDPSetCombineLERP(OVERLAY_DISP++, 0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, PRIMITIVE, TEXEL0, 0,
PRIMITIVE, 0);
gDPSetOtherMode(OVERLAY_DISP++,
G_AD_DISABLE | G_CD_DISABLE | G_CK_NONE | G_TC_FILT | G_TF_BILERP | G_TT_IA16 | G_TL_TILE |
G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE,
G_AC_NONE | G_ZS_PRIM | G_RM_XLU_SURF | G_RM_XLU_SURF2);
char* totalTimeText = GameplayStats_GetCurrentTime();
char* textPointer = &totalTimeText[0];
uint8_t textLength = strlen(textPointer);
uint16_t textureIndex = 0;
for (uint16_t i = 0; i < textLength; i++) {
if (totalTimeText[i] == ':' || totalTimeText[i] == '.') {
textureIndex = 10;
} else {
textureIndex = totalTimeText[i] - 48;
}
rectLeft = rectLeftOri + (i * 8);
rectTop = rectTopOri;
rectHeight = rectHeightOri;
// Load correct digit (or : symbol)
gDPLoadTextureBlock(OVERLAY_DISP++, ((u8*)digitTextures[textureIndex]), G_IM_FMT_I, G_IM_SIZ_8b, rectWidth,
rectHeight, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK,
G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
// Create dot image from the colon image.
if (totalTimeText[i] == '.') {
rectHeight = rectHeight / 2;
rectTop += 5;
rectLeft -= 1;
}
// Draw text shadow
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, 255);
gDPSetEnvColor(OVERLAY_DISP++, 255, 255, 255, 255);
gSPWideTextureRectangle(OVERLAY_DISP++, rectLeft << 2, rectTop << 2, (rectLeft + rectWidth) << 2,
(rectTop + rectHeight) << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10);
// Draw regular text. Change color based on if the timer is paused, running or the game is completed.
if (gSaveContext.sohStats.gameComplete) {
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 120, 255, 0, 255);
} else {
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, 255);
}
// Offset text so underlaying shadow is to the bottom right of the text.
rectLeft -= 1;
rectTop -= 1;
gSPWideTextureRectangle(OVERLAY_DISP++, rectLeft << 2, rectTop << 2, (rectLeft + rectWidth) << 2,
(rectTop + rectHeight) << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10);
}
CLOSE_DISPS(play->state.gfxCtx);
}
}
void Interface_Update(PlayState* play) {
static u8 D_80125B60 = 0;
static s16 sPrevTimeIncrement = 0;

View File

@ -1714,6 +1714,8 @@ void Play_Draw(PlayState* play) {
}
CLOSE_DISPS(gfxCtx);
Interface_DrawTotalGameplayTimer(play);
}
time_t Play_GetRealTime() {