Rando version warning on different builds (#2420)

* save build version to savefile

* adjust rando hash icons to use fade in/out

* add dialog message support on the file select screen and display rando version warning

* remove duplicated message functions and use stubbed play state instead for rando warning

* add major/minor/patch version saving to file and compare against

* use strncpy and memset for build version

* don't show rando warning one copy/erase screens

* review feedback

* Add german and french translations for rando warning

Co-authored-by: PurpleHato <linkvssangoku.jr@gmail.com>

---------

Co-authored-by: PurpleHato <linkvssangoku.jr@gmail.com>
This commit is contained in:
Adam Bird 2023-02-28 20:46:55 -05:00 committed by GitHub
parent 27d7cb0bc1
commit 1fc6a2f08f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 139 additions and 12 deletions

View File

@ -2416,7 +2416,7 @@ void GameOver_Update(PlayState* play);
void func_80110990(PlayState* play);
void func_801109B0(PlayState* play);
void Message_Init(PlayState* play);
void func_80112098(PlayState* play);
void Regs_InitData(PlayState* play);
void Title_Init(GameState* thisx);
void Title_PrintBuildInfo(Gfx** gfxp);
@ -2433,6 +2433,11 @@ void Heaps_Free(void);
CollisionHeader* BgCheck_GetCollisionHeader(CollisionContext* colCtx, s32 bgId);
// Exposing these methods to leverage them from the file select screen to render messages
void Message_OpenText(PlayState* play, u16 textId);
void Message_Decode(PlayState* play);
void Message_DrawText(PlayState* play, Gfx** gfxP);
#ifdef __cplusplus
#undef this
};

View File

@ -46,6 +46,9 @@ extern "C"
extern OSViMode osViModeFpalLan1;
extern u32 __additional_scanline;
extern u8 gBuildVersion[];
extern s16 gBuildVersionMajor;
extern s16 gBuildVersionMinor;
extern s16 gBuildVersionPatch;
extern u8 gBuildTeam[];
extern u8 gBuildDate[];
extern u8 gBuildMakeOption[];

View File

@ -54,6 +54,10 @@ typedef struct {
} Inventory; // size = 0x5E
typedef struct {
/* */ char buildVersion[50];
/* */ s16 buildVersionMajor;
/* */ s16 buildVersionMinor;
/* */ s16 buildVersionPatch;
/* */ u8 heartPieces;
/* */ u8 heartContainers;
/* */ u8 dungeonKeys[19];

View File

@ -41,6 +41,7 @@ typedef enum {
TEXT_WARP_RANDOM_REPLACED_TEXT = 0x9200,
TEXT_LAKE_HYLIA_WATER_SWITCH_SIGN = 0x346, // 0x3yy for cuttable sign range
TEXT_LAKE_HYLIA_WATER_SWITCH_NAVI = 0x1B3, // 0x1yy for Navi msg range
TEXT_RANDO_SAVE_VERSION_WARNING = 0x9300,
} TextIDs;
#ifdef __cplusplus

View File

@ -1692,6 +1692,9 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) {
if (textId == TEXT_MARKET_GUARD_NIGHT && CVarGetInteger("gMarketSneak", 0) && play->sceneNum == SCENE_ENTRA_N) {
messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, TEXT_MARKET_GUARD_NIGHT);
}
if (textId == TEXT_RANDO_SAVE_VERSION_WARNING) {
messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, TEXT_RANDO_SAVE_VERSION_WARNING);
}
if (messageEntry.textBoxType != -1) {
font->charTexBuf[0] = (messageEntry.textBoxType << 4) | messageEntry.textBoxPos;
switch (gSaveContext.language) {

View File

@ -5,6 +5,7 @@
#include "z64.h"
#include "functions.h"
#include "macros.h"
#include <variables.h>
#include <Hooks.h>
#include <libultraship/bridge.h>
@ -52,6 +53,11 @@ SaveManager::SaveManager() {
info.randoSave = 0;
info.requiresMasterQuest = 0;
info.requiresOriginal = 0;
info.buildVersionMajor = 0;
info.buildVersionMinor = 0;
info.buildVersionPatch = 0;
memset(&info.buildVersion, 0, sizeof(info.buildVersion));
}
}
@ -389,6 +395,12 @@ void SaveManager::InitMeta(int fileNum) {
// If the file is not marked as Master Quest, it could still theoretically be a rando save with all 12 MQ dungeons, in which case
// we don't actually require a vanilla OTR.
fileMetaInfo[fileNum].requiresOriginal = !gSaveContext.isMasterQuest && (!gSaveContext.n64ddFlag || gSaveContext.mqDungeonCount < 12);
fileMetaInfo[fileNum].buildVersionMajor = gSaveContext.sohStats.buildVersionMajor;
fileMetaInfo[fileNum].buildVersionMinor = gSaveContext.sohStats.buildVersionMinor;
fileMetaInfo[fileNum].buildVersionPatch = gSaveContext.sohStats.buildVersionPatch;
strncpy(fileMetaInfo[fileNum].buildVersion, gSaveContext.sohStats.buildVersion, sizeof(fileMetaInfo[fileNum].buildVersion) - 1);
fileMetaInfo[fileNum].buildVersion[sizeof(fileMetaInfo[fileNum].buildVersion) - 1] = 0;
}
void SaveManager::InitFile(bool isDebug) {
@ -559,6 +571,13 @@ void SaveManager::InitFileNormal() {
gSaveContext.infTable[29] = 1;
gSaveContext.sceneFlags[5].swch = 0x40000000;
gSaveContext.pendingSale = ITEM_NONE;
strncpy(gSaveContext.sohStats.buildVersion, (const char*) gBuildVersion, sizeof(gSaveContext.sohStats.buildVersion) - 1);
gSaveContext.sohStats.buildVersion[sizeof(gSaveContext.sohStats.buildVersion) - 1] = 0;
gSaveContext.sohStats.buildVersionMajor = gBuildVersionMajor;
gSaveContext.sohStats.buildVersionMinor = gBuildVersionMinor;
gSaveContext.sohStats.buildVersionPatch = gBuildVersionPatch;
//RANDOTODO (ADD ITEMLOCATIONS TO GSAVECONTEXT)
}
@ -1248,6 +1267,14 @@ void SaveManager::LoadBaseVersion3() {
SaveManager::Instance->LoadData("gsTokens", gSaveContext.inventory.gsTokens);
});
SaveManager::Instance->LoadStruct("sohStats", []() {
std::string buildVersion;
SaveManager::Instance->LoadData("buildVersion", buildVersion);
strncpy(gSaveContext.sohStats.buildVersion, buildVersion.c_str(), ARRAY_COUNT(gSaveContext.sohStats.buildVersion) - 1);
gSaveContext.sohStats.buildVersion[ARRAY_COUNT(gSaveContext.sohStats.buildVersion) - 1] = 0;
SaveManager::Instance->LoadData("buildVersionMajor", gSaveContext.sohStats.buildVersionMajor);
SaveManager::Instance->LoadData("buildVersionMinor", gSaveContext.sohStats.buildVersionMinor);
SaveManager::Instance->LoadData("buildVersionPatch", gSaveContext.sohStats.buildVersionPatch);
SaveManager::Instance->LoadData("heartPieces", gSaveContext.sohStats.heartPieces);
SaveManager::Instance->LoadData("heartContainers", gSaveContext.sohStats.heartContainers);
SaveManager::Instance->LoadArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) {
@ -1441,6 +1468,11 @@ void SaveManager::SaveBase() {
SaveManager::Instance->SaveData("gsTokens", gSaveContext.inventory.gsTokens);
});
SaveManager::Instance->SaveStruct("sohStats", []() {
SaveManager::Instance->SaveData("buildVersion", gSaveContext.sohStats.buildVersion);
SaveManager::Instance->SaveData("buildVersionMajor", gSaveContext.sohStats.buildVersionMajor);
SaveManager::Instance->SaveData("buildVersionMinor", gSaveContext.sohStats.buildVersionMinor);
SaveManager::Instance->SaveData("buildVersionPatch", gSaveContext.sohStats.buildVersionPatch);
SaveManager::Instance->SaveData("heartPieces", gSaveContext.sohStats.heartPieces);
SaveManager::Instance->SaveData("heartContainers", gSaveContext.sohStats.heartContainers);
SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) {
@ -1681,6 +1713,11 @@ void SaveManager::CopyZeldaFile(int from, int to) {
fileMetaInfo[to].randoSave = fileMetaInfo[from].randoSave;
fileMetaInfo[to].requiresMasterQuest = fileMetaInfo[from].requiresMasterQuest;
fileMetaInfo[to].requiresOriginal = fileMetaInfo[from].requiresOriginal;
fileMetaInfo[to].buildVersionMajor = fileMetaInfo[from].buildVersionMajor;
fileMetaInfo[to].buildVersionMinor = fileMetaInfo[from].buildVersionMinor;
fileMetaInfo[to].buildVersionPatch = fileMetaInfo[from].buildVersionPatch;
strncpy(fileMetaInfo[to].buildVersion, fileMetaInfo[from].buildVersion, sizeof(fileMetaInfo[to].buildVersion) - 1);
fileMetaInfo[to].buildVersion[sizeof(fileMetaInfo[to].buildVersion) - 1] = 0;
}
void SaveManager::DeleteZeldaFile(int fileNum) {

View File

@ -14,6 +14,10 @@ typedef struct {
u32 requiresOriginal;
u8 seedHash[5];
u8 randoSave;
char buildVersion[50];
s16 buildVersionMajor;
s16 buildVersionMinor;
s16 buildVersionPatch;
} SaveFileMetaInfo;
#ifdef __cplusplus

View File

@ -153,4 +153,13 @@ extern "C" void OTRMessage_Init()
"Tu as l'air de t'ennuyer. Tu veux&aller faire un tour?\x1B&%gOui&Non%w",
}
);
CustomMessageManager::Instance->CreateMessage(
customMessageTableID, TEXT_RANDO_SAVE_VERSION_WARNING,
{
TEXTBOX_TYPE_NONE_BOTTOM, TEXTBOX_POS_BOTTOM,
"This save was created on&a different version of SoH.&&Things may be broken.",
"Dieser Spielstand wurde auf einer&anderen Version von SoH erstellt.&&Es könnten Fehler auftreten.",
"Cette sauvegarde a été créée sur&une version différente de SoH.&Certaines fonctionnalités&peuvent être corrompues.",
}
);
}

View File

@ -1,4 +1,7 @@
const char gBuildVersion[] = "@PROJECT_BUILD_NAME@ (@CMAKE_PROJECT_VERSION_MAJOR@.@CMAKE_PROJECT_VERSION_MINOR@.@CMAKE_PROJECT_VERSION_PATCH@)";
const int gBuildVersionMajor = @CMAKE_PROJECT_VERSION_MAJOR@;
const int gBuildVersionMinor = @CMAKE_PROJECT_VERSION_MINOR@;
const int gBuildVersionPatch = @CMAKE_PROJECT_VERSION_PATCH@;
const char gBuildTeam[] = "@PROJECT_TEAM@";
const char gBuildDate[] = __DATE__ " " __TIME__;
const char gBuildMakeOption[] = "";

View File

@ -163,7 +163,7 @@ void Message_Init(PlayState* play) {
YREG(31) = 0;
}
void func_80111070(void) {
void Regs_InitDataImpl(void) {
YREG(8) = 10;
YREG(14) = 0;
YREG(15) = 0;
@ -572,6 +572,6 @@ void func_80111070(void) {
VREG(92) = -63;
}
void func_80112098(PlayState* play) {
func_80111070();
void Regs_InitData(PlayState* play) {
Regs_InitDataImpl();
}

View File

@ -463,7 +463,7 @@ void Play_Init(GameState* thisx) {
play->cameraPtrs[MAIN_CAM]->uid = 0;
play->activeCamera = MAIN_CAM;
func_8005AC48(&play->mainCamera, 0xFF);
func_80112098(play);
Regs_InitData(play);
Message_Init(play);
GameOver_Init(play);
SoundSource_InitAll(play);

View File

@ -11,6 +11,8 @@
#include "objects/gameplay_keep/gameplay_keep.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
#define NORMAL_QUEST 0
#define MASTER_QUEST 1
#define RANDOMIZER_QUEST 2
@ -387,12 +389,17 @@ void DrawSeedHashSprites(FileChooseContext* this) {
gDPPipeSync(POLY_OPA_DISP++);
gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
if (this->windowRot == 0 || (this->configMode == CM_QUEST_MENU && this->questType[this->buttonIndex] == RANDOMIZER_QUEST)) {
if (this->selectMode == SM_CONFIRM_FILE) {
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0xFF, 0xFF, 0xFF, this->fileInfoAlpha[this->buttonIndex]);
// Draw icons on the main menu, when a rando file is selected, and when quest selection is set to rando
if ((this->configMode == CM_MAIN_MENU &&
(this->selectMode != SM_CONFIRM_FILE || Save_GetSaveMetaInfo(this->selectedFileIndex)->randoSave == 1)) ||
(this->configMode == CM_QUEST_MENU && this->questType[this->buttonIndex] == RANDOMIZER_QUEST)) {
if (this->fileInfoAlpha[this->selectedFileIndex] > 0) {
// Use file info alpha to match fading
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0xFF, 0xFF, 0xFF, this->fileInfoAlpha[this->selectedFileIndex]);
u16 xStart = 64;
// Draw Seed Icons
// Draw Seed Icons for specific file
for (unsigned int i = 0; i < 5; i++) {
if (Save_GetSaveMetaInfo(this->selectedFileIndex)->randoSave == 1) {
SpriteLoad(this, GetSeedTexture(Save_GetSaveMetaInfo(this->selectedFileIndex)->seedHash[i]));
@ -402,8 +409,15 @@ void DrawSeedHashSprites(FileChooseContext* this) {
}
}
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0xFF, 0xFF, 0xFF, this->fileButtonAlpha[this->buttonIndex]);
// Fade top seed icons based on main menu fade and if save supports rando
u8 alpha = MAX(this->optionButtonAlpha, Save_GetSaveMetaInfo(this->selectedFileIndex)->randoSave == 1 ? 0xFF : 0);
if (alpha >= 200) {
alpha = 0xFF;
}
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0xFF, 0xFF, 0xFF, alpha);
// Draw Seed Icons for spoiler log
if (strnlen(CVarGetString("gSpoilerLog", ""), 1) != 0 && fileSelectSpoilerFileLoaded) {
u16 xStart = 64;
for (unsigned int i = 0; i < 5; i++) {
@ -1340,6 +1354,47 @@ void FileChoose_DrawFileInfo(GameState* thisx, s16 fileIndex, s16 isActive) {
}
}
}
// Use file info alpha to match fading
u8 textAlpha = this->fileInfoAlpha[fileIndex];
if (textAlpha >= 200) {
textAlpha = 255;
}
// Draw rando seed warning when build version doesn't match for Major or Minor number
if (Save_GetSaveMetaInfo(fileIndex)->randoSave == 1 &&
this->menuMode == FS_MENU_MODE_SELECT &&
(gBuildVersionMajor != Save_GetSaveMetaInfo(fileIndex)->buildVersionMajor ||
gBuildVersionMinor != Save_GetSaveMetaInfo(fileIndex)->buildVersionMinor)) {
// Stub out a dummy play state to be able to use the dialog system (MessageCtx)
PlayState dummyPlay;
PlayState* dummyPlayPtr = &dummyPlay;
// Set the MessageCtx and GameState onto the dummy play state
dummyPlayPtr->msgCtx = this->msgCtx;
dummyPlayPtr->state = this->state;
// Load the custom text ID without doing a textbox
Message_OpenText(dummyPlayPtr, TEXT_RANDO_SAVE_VERSION_WARNING);
// Force the context into message print mode
dummyPlayPtr->msgCtx.msgMode = MSGMODE_TEXT_NEXT_MSG;
Message_Decode(dummyPlayPtr);
// Set the draw pos to end of text to render it all at once
dummyPlayPtr->msgCtx.textDrawPos = dummyPlayPtr->msgCtx.decodedTextLen;
dummyPlayPtr->msgCtx.textColorAlpha = textAlpha;
// Set position and spacing values
R_TEXT_LINE_SPACING = 10;
R_TEXT_INIT_XPOS = 128;
R_TEXT_INIT_YPOS = 154;
Gfx* gfx = Graph_GfxPlusOne(POLY_OPA_DISP);
Message_DrawText(dummyPlayPtr, &gfx);
POLY_OPA_DISP = gfx;
}
}
CLOSE_DISPS(this->state.gfxCtx);
@ -2658,6 +2713,9 @@ void FileChoose_Init(GameState* thisx) {
DmaMgr_SendRequest1(this->parameterSegment, (u32)_parameter_staticSegmentRomStart, size, __FILE__,
__LINE__);
// Load some registers used by the dialog system
Regs_InitData(NULL); // Passing in NULL as we dont have a playstate, and it isn't used in the func
Matrix_Init(&this->state);
View_Init(&this->view, this->state.gfxCtx);
this->state.main = FileChoose_Main;