diff --git a/soh/assets/custom/textures/parameter_static/gBossSoul.rgba32.png b/soh/assets/custom/textures/parameter_static/gBossSoul.rgba32.png new file mode 100755 index 000000000..9fe2ac404 Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gBossSoul.rgba32.png differ diff --git a/soh/assets/soh_assets.h b/soh/assets/soh_assets.h index e8c947d0e..3f9039e35 100644 --- a/soh/assets/soh_assets.h +++ b/soh/assets/soh_assets.h @@ -97,6 +97,9 @@ static const ALIGN_ASSET(2) char gArrowDownTex[] = dgArrowDown; #define dgTriforcePiece "__OTR__textures/parameter_static/gTriforcePiece" static const ALIGN_ASSET(2) char gTriforcePieceTex[] = dgTriforcePiece; +#define dgBossSoul "__OTR__textures/parameter_static/gBossSoul" +static const ALIGN_ASSET(2) char gBossSoulTex[] = dgBossSoul; + #define dgFileSelMQButtonTex "__OTR__textures/title_static/gFileSelMQButtonTex" static const ALIGN_ASSET(2) char gFileSelMQButtonTex[] = dgFileSelMQButtonTex; diff --git a/soh/include/z64.h b/soh/include/z64.h index 97f68341d..36fe91d69 100644 --- a/soh/include/z64.h +++ b/soh/include/z64.h @@ -920,7 +920,10 @@ typedef struct { /* 0x0266 */ u8 worldMapPoints[20]; // 0 = hidden; 1 = displayed; 2 = highlighted /* 0x027A */ u8 tradeQuestLocation; /* 0x027C */ SkelAnime playerSkelAnime; -} PauseContext; // size = 0x2C0 + // #region SOH [Randomizer] + /* 0x02C0 */ u8 randoQuestMode; // 0 = Off (normal quest menu); 1 = On (Misc Collectibles menu) + // #endregion +} PauseContext; // size = 0x2C1 typedef enum { /* 00 */ GAMEOVER_INACTIVE, diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 5f8a10d8e..d96a3ab55 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -790,6 +790,7 @@ public: public: static void SetSceneFlag(int16_t sceneNum, int16_t flagType, int16_t flag); static void UnsetSceneFlag(int16_t sceneNum, int16_t flagType, int16_t flag); + static bool CheckFlag(int16_t flagType, int16_t flag); static void SetFlag(int16_t flagType, int16_t chestNum); static void UnsetFlag(int16_t flagType, int16_t chestNum); static void AddOrRemoveHealthContainers(int16_t amount); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp index 5e61f704d..fb428d1ae 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp @@ -205,6 +205,23 @@ void GameInteractor::RawAction::UnsetSceneFlag(int16_t sceneNum, int16_t flagTyp } }; +bool GameInteractor::RawAction::CheckFlag(int16_t flagType, int16_t flag) { + switch (flagType) { + case FlagType::FLAG_EVENT_CHECK_INF: + return Flags_GetEventChkInf(flag); + case FlagType::FLAG_ITEM_GET_INF: + return Flags_GetItemGetInf(flag); + case FlagType::FLAG_INF_TABLE: + return Flags_GetInfTable(flag); + case FlagType::FLAG_EVENT_INF: + return Flags_GetEventInf(flag); + case FlagType::FLAG_RANDOMIZER_INF: + return Flags_GetRandomizerInf(static_cast(flag)); + case FlagType::FLAG_GS_TOKEN: + return GET_GS_FLAGS((flag & 0x1F00) >> 8); + } +} + void GameInteractor::RawAction::SetFlag(int16_t flagType, int16_t flag) { switch (flagType) { case FlagType::FLAG_EVENT_CHECK_INF: diff --git a/soh/soh/Enhancements/kaleido.cpp b/soh/soh/Enhancements/kaleido.cpp new file mode 100644 index 000000000..6d66a0578 --- /dev/null +++ b/soh/soh/Enhancements/kaleido.cpp @@ -0,0 +1,465 @@ +#include "kaleido.h" + +#include "soh/frame_interpolation.h" + +extern "C" { +#include "z64.h" +#include "functions.h" +#include "macros.h" +#include "variables.h" +#include +#include +extern PlayState* gPlayState; +} +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh_assets.h" +#include "textures/icon_item_static/icon_item_static.h" +#include "consolevariablebridge.h" +#include "soh/Enhancements/cosmetics/cosmeticsTypes.h" + +#include + +extern "C" { +void KaleidoScope_MoveCursorToSpecialPos(PlayState* play, u16 specialPos); +} + +namespace Rando { + + void KaleidoEntryIcon::LoadIconTex(std::vector* mEntryDl) { + if (mIconFormat == G_IM_FMT_IA) { + if (mIconSize == G_IM_SIZ_8b) { + Gfx iconTexture[] = { gsDPLoadTextureBlock(mIconResourceName, G_IM_FMT_IA, G_IM_SIZ_8b, mIconWidth, mIconHeight, 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) }; + mEntryDl->insert(mEntryDl->end(), std::begin(iconTexture), std::end(iconTexture)); + } + } else if (mIconFormat == G_IM_FMT_RGBA) { + if (mIconSize == G_IM_SIZ_32b) { + Gfx iconTexture[] = { gsDPLoadTextureBlock(mIconResourceName, G_IM_FMT_RGBA, G_IM_SIZ_32b, mIconWidth, mIconHeight, 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) }; + mEntryDl->insert(mEntryDl->end(), std::begin(iconTexture), std::end(iconTexture)); + } + } + } + + KaleidoEntry::KaleidoEntry(int16_t x, int16_t y, std::string text) : mX(x), mY(y), mText(std::move(text)) { + mHeight = 0; + mWidth = 0; + vtx = nullptr; + } + + void KaleidoEntry::SetYOffset(int yOffset) { + mY = yOffset; + } + + void KaleidoEntryIcon::Draw(PlayState* play, std::vector* mEntryDl) { + if (vtx == nullptr) { + return; + } + size_t numChar = mText.length(); + if (numChar == 0) { + return; + } + + Color_RGBA8 textColor = { 255, 255, 255, 255 }; + if (mAchieved) { + textColor = { 0x98, 0xFF, 0x44, 255 }; + } + + Matrix_Translate(mX, mY, 0.0f, MTXMODE_APPLY); + + mEntryDl->push_back(gsSPMatrix(Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), G_MTX_PUSH | G_MTX_LOAD | G_MTX_MODELVIEW)); + + // icon + if (!mAchieved) { + mEntryDl->push_back(gsDPSetGrayscaleColor(109, 109, 109, 255)); + mEntryDl->push_back(gsSPGrayscale(true)); + } + mEntryDl->push_back(gsDPSetPrimColor(0, 0, mIconColor.r, mIconColor.g, mIconColor.b, mIconColor.a)); + mEntryDl->push_back(gsSPVertex(vtx, 4, 0)); + LoadIconTex(mEntryDl); + mEntryDl->push_back(gsSP1Quadrangle(0, 2, 3, 1, 0)); + mEntryDl->push_back(gsSPGrayscale(false)); + + // text + mEntryDl->push_back(gsDPSetPrimColor(0, 0, textColor.r, textColor.g, textColor.b, textColor.a)); + for (size_t i = 0, vtxGroup = 0; i < numChar; i++) { + uint16_t texIndex = mText[i] - 32; + + // A maximum of 64 Vtx can be loaded at once by gSPVertex, or basically 16 characters + // handle loading groups of 16 chars at a time until there are no more left to load. + // By this point 4 vertices have already been loaded for the preceding icon. + if (i % 16 == 0) { + size_t numVtxToLoad = std::min(numChar - i, 16) * 4; + mEntryDl->push_back(gsSPVertex(&vtx[4 + (vtxGroup * 16 * 4)], numVtxToLoad, 0)); + vtxGroup++; + } + + if (texIndex != 0) { + auto texture = reinterpret_cast(Font_FetchCharTexture(texIndex)); + auto vertexStart = static_cast(4 * (i % 16)); + + Gfx charTexture[] = {gsDPLoadTextureBlock_4b(texture, G_IM_FMT_I, FONT_CHAR_TEX_WIDTH, + FONT_CHAR_TEX_HEIGHT, 0, G_TX_NOMIRROR | G_TX_CLAMP, + G_TX_NOMIRROR | G_TX_CLAMP, + G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD)}; + mEntryDl->insert(mEntryDl->end(), std::begin(charTexture), std::end(charTexture)); + mEntryDl->push_back( + gsSP1Quadrangle(vertexStart, vertexStart + 2, vertexStart + 3, vertexStart + 1, 0)); + } + } + mEntryDl->push_back(gsSPPopMatrix(G_MTX_MODELVIEW)); + } + + Kaleido::Kaleido() { + const auto ctx = Rando::Context::GetInstance(); + int yOffset = 2; + mEntries.push_back(std::make_shared(gRupeeCounterIconTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16, + Color_RGBA8{ 0xC8, 0xFF, 0x64, 255 }, FlagType::FLAG_RANDOMIZER_INF, + static_cast(RAND_INF_GREG_FOUND), 0, yOffset, "Greg")); + yOffset += 18; + if (ctx->GetOption(RSK_TRIFORCE_HUNT)) { + mEntries.push_back( + std::make_shared( + gTriforcePieceTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255,255,255,255 }, 0, + yOffset, reinterpret_cast(&gSaveContext.triforcePiecesCollected), + ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_REQUIRED).GetSelectedOptionIndex() + 1, + ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).GetSelectedOptionIndex() + 1)); + yOffset += 18; + } + if (ctx->GetOption(RSK_SHUFFLE_OCARINA_BUTTONS)) { + mEntries.push_back(std::make_shared(0, yOffset)); + yOffset += 18; + } + if (ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).IsNot(RO_BOSS_SOULS_OFF)) { + static const char* bossSoulNames[] = { + "Gohma's Soul", + "King Dodongo's Soul", + "Barinade's Soul", + "Phantom Ganon's Soul", + "Volvagia's Soul", + "Morpha's Soul", + "Bongo Bongo's Soul", + "Twinrova's Soul", + }; + for (int i = RAND_INF_GOHMA_SOUL; i < RAND_INF_GANON_SOUL; i++) { + mEntries.push_back( + std::make_shared( + gBossSoulTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 }, + FlagType::FLAG_RANDOMIZER_INF, i, 0, yOffset, bossSoulNames[i - RAND_INF_GOHMA_SOUL] + ) + ); + yOffset += 18; + } + } + if (ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).Is(RO_BOSS_SOULS_ON_PLUS_GANON)) { + mEntries.push_back( + std::make_shared( + gBossSoulTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 }, + FlagType::FLAG_RANDOMIZER_INF, RAND_INF_GANON_SOUL, 0, yOffset, "Ganon's Soul" + ) + ); + yOffset += 18; + } + } + + extern "C" { + void FrameInterpolation_RecordCloseChild(void); + void FrameInterpolation_RecordOpenChild(const void* a, int b); + } + + void Kaleido::Draw(PlayState* play) { + if (play == nullptr || mEntries.empty()) { + return; + } + PauseContext* pauseCtx = &play->pauseCtx; + Input* input = &play->state.input[0]; + mEntryDl.clear(); + OPEN_DISPS(play->state.gfxCtx); + mEntryDl.push_back(gsDPPipeSync()); + Gfx_SetupDL_42Opa(play->state.gfxCtx); + mEntryDl.push_back(gsDPSetCombineMode(G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM)); + + // Move the matrix origin to the top-left corner of the kaleido page + Matrix_Translate(-108.f, 58.f, 0.0f, MTXMODE_APPLY); + // Invert the matrix to render vertices with positive going down + Matrix_Scale(1.0f, -1.0f, 1.0f, MTXMODE_APPLY); + // The scrolling logic is in here because the built in kaleido input throttling happens + // in its Draw functions, which get called after their update functions. I hate it but fixing + // it would be a much larger Kaleido change. + bool shouldScroll = false; + bool dpad = CVarGetInteger(CVAR_SETTING("DPadOnPause"), 0); + if ((!pauseCtx->unk_1E4 || (pauseCtx->unk_1E4 == 5) || (pauseCtx->unk_1E4 == 8)) && + (pauseCtx->pageIndex == PAUSE_QUEST)) { + if (pauseCtx->cursorSpecialPos == 0) { + if ((pauseCtx->stickRelY > 30) || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DUP))) { + if (mTopIndex > 0) { + mTopIndex--; + shouldScroll = true; + } + } else if ((pauseCtx->stickRelY < -30) || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DDOWN))) { + if (mTopIndex + mNumVisible < mEntries.size()) { + mTopIndex++; + shouldScroll = true; + } + } + if ((pauseCtx->stickRelX < -30) || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DLEFT))) { + KaleidoScope_MoveCursorToSpecialPos(play, PAUSE_CURSOR_PAGE_LEFT); + pauseCtx->unk_1E4 = 0; + } else if ((pauseCtx->stickRelX > 30) || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DRIGHT))) { + KaleidoScope_MoveCursorToSpecialPos(play, PAUSE_CURSOR_PAGE_RIGHT); + pauseCtx->unk_1E4 = 0; + } + } else if (pauseCtx->cursorSpecialPos == PAUSE_CURSOR_PAGE_LEFT) { + if ((pauseCtx->stickRelX > 30) || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DRIGHT))) { + pauseCtx->cursorSpecialPos = 0; + Audio_PlaySoundGeneral(NA_SE_SY_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); + } + } else if (pauseCtx->cursorSpecialPos == PAUSE_CURSOR_PAGE_RIGHT) { + if ((pauseCtx->stickRelX < -30) || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DLEFT))) { + pauseCtx->cursorSpecialPos = 0; + Audio_PlaySoundGeneral(NA_SE_SY_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); + } + } + } + int yOffset = 2; + for (int i = mTopIndex; i < (mTopIndex + mNumVisible) && i < mEntries.size(); i++) { + auto& entry = mEntries[i]; + if (shouldScroll) { + entry->SetYOffset(yOffset); + yOffset += 18; + Audio_PlaySoundGeneral(NA_SE_SY_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); + } + Matrix_Push(); + entry->Draw(play, &mEntryDl); + Matrix_Pop(); + } + + mEntryDl.push_back(gsSPEndDisplayList()); + gSPDisplayList(POLY_KAL_DISP++, mEntryDl.data()); + CLOSE_DISPS(play->state.gfxCtx); + } + + void Kaleido::Update(PlayState *play) { + for(int i = mTopIndex; i < (mTopIndex + mNumVisible) && i < mEntries.size(); i++) { + const auto& entry = mEntries[i]; + entry->Update(play); + } + } + + extern "C" void RandoKaleido_DrawMiscCollectibles(PlayState* play) { + OTRGlobals::Instance->gRandoContext->GetKaleido()->Draw(play); + } + + extern "C" void RandoKaleido_UpdateMiscCollectibles(int16_t inDungeonScene) { + PauseContext* pauseCtx = &gPlayState->pauseCtx; + if (pauseCtx->randoQuestMode && pauseCtx->pageIndex == PAUSE_QUEST) { + OTRGlobals::Instance->gRandoContext->GetKaleido()->Update(gPlayState); + } + } + + KaleidoEntryIconFlag::KaleidoEntryIconFlag(const char *iconResourceName, int iconFormat, int iconSize, int iconWidth, + int iconHeight, Color_RGBA8 iconColor, FlagType flagType, int flag, + int16_t x, int16_t y, std::string name) : + mFlagType(flagType), mFlag(flag), + KaleidoEntryIcon(iconResourceName, iconFormat, iconSize, iconWidth, iconHeight, iconColor, x, y, std::move(name)) { + BuildVertices(); + } + + void KaleidoEntryIconFlag::Update(PlayState* play) { + mAchieved = GameInteractor::RawAction::CheckFlag(mFlagType, static_cast(mFlag)); + } + + KaleidoEntryIconCountRequired::KaleidoEntryIconCountRequired(const char *iconResourceName, int iconFormat, + int iconSize, int iconWidth, int iconHeight, Color_RGBA8 iconColor, int16_t x, int16_t y, int* watch, + int required, int total) : mWatch(watch), mRequired(required), mTotal(total), + KaleidoEntryIcon(iconResourceName, iconFormat, iconSize, iconWidth, iconHeight, iconColor, x, y) { + mCount = *mWatch; + BuildText(); + BuildVertices(); + } + + void KaleidoEntryIconCountRequired::BuildText() { + std::ostringstream totals; + totals << mCount; + if (mRequired != 0 && mCount < mRequired) { + totals << '/' << mRequired; + } + if (mTotal >= mRequired && mCount >= mRequired) { + totals << '/' << mTotal; + } + mText = totals.str(); + } + + void KaleidoEntryIcon::BuildVertices() { + int offsetY = 0; + int offsetX = 0; + // 4 vertices per character, plus one for the preceding icon. + Vtx* vertices = (Vtx*)calloc(sizeof(Vtx[4]), mText.length() + 1); + // Vertex for the preceding icon. + Interface_CreateQuadVertexGroup(vertices, offsetX, offsetY, mIconWidth, mIconHeight, 0); + offsetX += 18; + for (size_t i = 0; i < mText.length(); i++) { + auto charIndex = static_cast(mText[i] - 32); + int charWidth = 0; + if (charIndex >= 0) { + charWidth = static_cast(Message_GetCharacterWidth(charIndex) * (100.0f / R_TEXT_CHAR_SCALE)); + } + Interface_CreateQuadVertexGroup(&(vertices)[(i + 1) * 4], offsetX, offsetY, charWidth, 16, 0); + offsetX += charWidth; + } + offsetY += FONT_CHAR_TEX_HEIGHT; + mWidth = static_cast(offsetX); + mHeight = static_cast(offsetY); + + vertices[1].v.ob[0] = 16; + vertices[2].v.ob[1] = 16; + vertices[3].v.ob[0] = 16; + vertices[3].v.ob[1] = 16; + vtx = vertices; + } + + KaleidoEntryIcon::KaleidoEntryIcon(const char *iconResourceName, int iconFormat, int iconSize, int iconWidth, + int iconHeight, Color_RGBA8 iconColor, int16_t x, int16_t y, std::string text) + : mIconResourceName(iconResourceName), mIconFormat(iconFormat), mIconSize(iconSize), + mIconWidth(iconWidth), mIconHeight(iconHeight), mIconColor(iconColor), + KaleidoEntry(x, y, std::move(text)) {} + + void KaleidoEntryIcon::RebuildVertices() { + free(vtx); + vtx = nullptr; + BuildVertices(); + } + + void KaleidoEntryIconCountRequired::Update(PlayState *play) { + if (mCount != *mWatch) { + mCount = *mWatch; + BuildText(); + RebuildVertices(); + mAchieved = mCount >= mRequired; + } + + } + + KaleidoEntryOcarinaButtons::KaleidoEntryOcarinaButtons(int16_t x, int16_t y) : + KaleidoEntryIcon(gItemIconOcarinaOfTimeTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, + Color_RGBA8{ 255, 255, 255, 255 }, x, y, "\x9F\xA5\xA6\xA7\xA8") { + CalculateColors(); + BuildVertices(); + } + + void KaleidoEntryOcarinaButtons::CalculateColors() { + Color_RGB8 aButtonColor = { 80, 150, 255 }; + if (CVarGetInteger(CVAR_COSMETIC("HUD.AButton.Changed"), 0)) { + aButtonColor = CVarGetColor24(CVAR_COSMETIC("HUD.AButton.Value"), aButtonColor); + } else if (CVarGetInteger(CVAR_COSMETIC("DefaultColorScheme"), COLORSCHEME_N64) == COLORSCHEME_GAMECUBE) { + aButtonColor = { 80, 255, 150}; + } + mButtonColors[0] = { aButtonColor.r, aButtonColor.g, aButtonColor.b, 255 }; + Color_RGB8 cButtonsColor = { 255, 255, 50 }; + Color_RGB8 cUpButtonColor = cButtonsColor; + Color_RGB8 cDownButtonColor = cButtonsColor; + Color_RGB8 cLeftButtonColor = cButtonsColor; + Color_RGB8 cRightButtonColor = cButtonsColor; + if (CVarGetInteger(CVAR_COSMETIC("HUD.CButtons.Changed"), 0)) { + cUpButtonColor = CVarGetColor24(CVAR_COSMETIC("HUD.CButtons.Value"), cButtonsColor); + cDownButtonColor = CVarGetColor24(CVAR_COSMETIC("HUD.CButtons.Value"), cButtonsColor); + cLeftButtonColor = CVarGetColor24(CVAR_COSMETIC("HUD.CButtons.Value"), cButtonsColor); + cRightButtonColor = CVarGetColor24(CVAR_COSMETIC("HUD.CButtons.Value"), cButtonsColor); + } + if (CVarGetInteger(CVAR_COSMETIC("HUD.CUpButton.Changed"), 0)) { + cUpButtonColor = CVarGetColor24(CVAR_COSMETIC("HUD.CUpButton.Value"), cUpButtonColor); + } + if (CVarGetInteger(CVAR_COSMETIC("HUD.CDownButton.Changed"), 0)) { + cDownButtonColor = CVarGetColor24(CVAR_COSMETIC("HUD.CDownButton.Value"), cDownButtonColor); + } + if (CVarGetInteger(CVAR_COSMETIC("HUD.CLeftButton.Changed"), 0)) { + cLeftButtonColor = CVarGetColor24(CVAR_COSMETIC("HUD.CLeftButton.Value"), cLeftButtonColor); + } + if (CVarGetInteger(CVAR_COSMETIC("HUD.CRightButton.Changed"), 0)) { + cRightButtonColor = CVarGetColor24(CVAR_COSMETIC("HUD.CRightButton.Value"), cRightButtonColor); + } + mButtonColors[1] = { cUpButtonColor.r, cUpButtonColor.g, cUpButtonColor.b, 255 }; + mButtonColors[2] = { cDownButtonColor.r, cDownButtonColor.g, cDownButtonColor.b, 255 }; + mButtonColors[3] = { cLeftButtonColor.r, cLeftButtonColor.g, cLeftButtonColor.b, 255 }; + mButtonColors[4] = { cRightButtonColor.r, cRightButtonColor.g, cRightButtonColor.b, 255 }; + } + + void KaleidoEntryOcarinaButtons::Update(PlayState *play) { + mButtonCollected[0] = GameInteractor::RawAction::CheckFlag(FLAG_RANDOMIZER_INF, RAND_INF_HAS_OCARINA_A) > 0; + mButtonCollected[1] = GameInteractor::RawAction::CheckFlag(FLAG_RANDOMIZER_INF, RAND_INF_HAS_OCARINA_C_UP) > 0; + mButtonCollected[2] = GameInteractor::RawAction::CheckFlag(FLAG_RANDOMIZER_INF, RAND_INF_HAS_OCARINA_C_DOWN) > 0; + mButtonCollected[3] = GameInteractor::RawAction::CheckFlag(FLAG_RANDOMIZER_INF, RAND_INF_HAS_OCARINA_C_LEFT) > 0; + mButtonCollected[4] = GameInteractor::RawAction::CheckFlag(FLAG_RANDOMIZER_INF, RAND_INF_HAS_OCARINA_C_RIGHT) > 0; + CalculateColors(); + mAchieved = false; + for (int i = 0; i < mButtonCollected.size(); i++) { + if (!mButtonCollected[i]) { + mButtonColors[i] = Color_RGBA8{ 109, 109, 109, 255 }; + } else { + mAchieved = true; + } + } + } + + void KaleidoEntryOcarinaButtons::Draw(PlayState *play, std::vector* mEntryDl) { + if (vtx == nullptr) { + return; + } + size_t numChar = mText.length(); + if (numChar == 0) { + return; + } + + Matrix_Translate(mX, mY, 0.0f, MTXMODE_APPLY); +// Matrix_Scale(0.75f, 0.75f, 0.75f, MTXMODE_APPLY); + + mEntryDl->push_back(gsSPMatrix(Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), G_MTX_PUSH | G_MTX_LOAD | G_MTX_MODELVIEW)); + + // icon + if (!mAchieved) { + mEntryDl->push_back(gsDPSetGrayscaleColor(109, 109, 109, 255)); + mEntryDl->push_back(gsSPGrayscale(true)); + } + mEntryDl->push_back(gsDPSetPrimColor(0, 0, mIconColor.r, mIconColor.g, mIconColor.b, mIconColor.a)); + mEntryDl->push_back(gsSPVertex(vtx, 4, 0)); + LoadIconTex(mEntryDl); + mEntryDl->push_back(gsSP1Quadrangle(0, 2, 3, 1, 0)); + mEntryDl->push_back(gsSPGrayscale(false)); + + // text + for (size_t i = 0, vtxGroup = 0; i < numChar; i++) { + mEntryDl->push_back(gsDPSetPrimColor(0, 0, mButtonColors[i].r, mButtonColors[i].g, mButtonColors[i].b, mButtonColors[i].a)); + uint16_t texIndex = mText[i] - 32; + + // A maximum of 64 Vtx can be loaded at once by gSPVertex, or basically 16 characters + // handle loading groups of 16 chars at a time until there are no more left to load. + // By this point 4 vertices have already been loaded for the preceding icon. + if (i % 16 == 0) { + size_t numVtxToLoad = std::min(numChar - i, 16) * 4; + mEntryDl->push_back(gsSPVertex(&vtx[4 + (vtxGroup * 16 * 4)], numVtxToLoad, 0)); + vtxGroup++; + } + + if (texIndex != 0) { + auto texture = reinterpret_cast(Font_FetchCharTexture(texIndex)); + auto vertexStart = static_cast(4 * (i % 16)); + + Gfx charTexture[] = {gsDPLoadTextureBlock_4b(texture, G_IM_FMT_I, FONT_CHAR_TEX_WIDTH, + FONT_CHAR_TEX_HEIGHT, 0, G_TX_NOMIRROR | G_TX_CLAMP, + G_TX_NOMIRROR | G_TX_CLAMP, + G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD)}; + mEntryDl->insert(mEntryDl->end(), std::begin(charTexture), std::end(charTexture)); + mEntryDl->push_back( + gsSP1Quadrangle(vertexStart, vertexStart + 2, vertexStart + 3, vertexStart + 1, 0)); + } + } + mEntryDl->push_back(gsSPPopMatrix(G_MTX_MODELVIEW)); + } +} // Rando + +void RandoKaleido_RegisterHooks() { + GameInteractor::Instance->RegisterGameHook(RandoKaleido_UpdateMiscCollectibles); +} \ No newline at end of file diff --git a/soh/soh/Enhancements/kaleido.h b/soh/soh/Enhancements/kaleido.h new file mode 100644 index 000000000..db86a817e --- /dev/null +++ b/soh/soh/Enhancements/kaleido.h @@ -0,0 +1,176 @@ +#ifndef KALEIDO_H +#define KALEIDO_H +#include + + +#ifdef __cplusplus +#include +#include +#include + +namespace Rando { + +/** + * Base class for all Kaleido Entries in our Rando Collectibles list. + * Stores the common aspects such as the line of text, positioning, + * vertices, and whether or not the goal has been achieved (subclasses + * can use this to change their rendering). Also sets an interface for + * subclasses to declare their Draw and Update functions. + */ +class KaleidoEntry { +public: + /** + * @brief Constructor for Base KaleidoEntry class. Sets the position and + * initial value of the line of text. + * @param x x coordinate relative to the current matrix origin. + * @param y y coordinate relative to the current matrix origin. + * @param text the initial value of the line of text. Can be omitted for an + * empty string. + */ + KaleidoEntry(int16_t x, int16_t y, std::string text = ""); + virtual void Draw(PlayState* play, std::vector* mEntryDl) = 0; + virtual void Update(PlayState* play) = 0; + void SetYOffset(int yOffset); +protected: + int16_t mX; + int16_t mY; + int16_t mHeight; + int16_t mWidth; + Vtx* vtx; + std::string mText; + bool mAchieved = false; + +}; + +/** + * Subclass of KaleidoEntry intended to be a parent class for any KaleidoEntry subclasses + * that wish to render an Icon at the start of their line. + */ +class KaleidoEntryIcon : public KaleidoEntry { +public: + /** + * @param iconResourceName resource name of the icon to draw + * @param iconFormat flag representing the format of the icon (i.e. G_IM_FMT_IA) + * @param iconSize flag representing the size of the icon in bytes (i.e. G_IM_SIZ_8b) + * @param iconWidth pixel width of the source icon image + * @param iconHeight pixel height of the source icon image + * @param iconColor Color to shade the icon with. This may be ignored for certain icon formats + * @param x x coordinate of the location to draw this relative to the parent matrix's origin. + * @param y y coordinate of the location to draw this relative to the parent matrix's origin. + * @param text text to draw to the right of the icon. + */ + KaleidoEntryIcon(const char* iconResourceName, int iconFormat, int iconSize, int iconWidth, int iconHeight, + Color_RGBA8 iconColor, int16_t x, int16_t y, std::string text = ""); + void Draw(PlayState* play, std::vector* mEntryDl) override; + void RebuildVertices(); +protected: + const char* mIconResourceName; + int mIconFormat; + int mIconSize; + int mIconWidth; + int mIconHeight; + Color_RGBA8 mIconColor; + + void BuildVertices(); + void LoadIconTex(std::vector* mEntryDl); +}; + +/** + * Class representing a Kaleido Entry that can be represented with an icon + * that is either colored in or Grayscale according to a flag + */ +class KaleidoEntryIconFlag : public KaleidoEntryIcon { +public : + /** + * @param iconResourceName resource name of the icon to draw + * @param iconFormat flag representing the format of the icon (i.e. G_IM_FMT_IA) + * @param iconSize flag representing the size of the icon in bytes (i.e. G_IM_SIZ_8b) + * @param iconWidth pixel width of the source icon image + * @param iconHeight pixel height of the source icon image + * @param iconColor Color to shade the icon with. This may be ignored for certain icon formats + * @param flagType FlagType of the flag to check for (i.e. FlagType::FLAG_RANDOMIZER_INF + * @param flag flag to check for. An integer can be provided but enum values should be preferred + * @param x x coordinate of the location to draw this relative to the parent matrix's origin. + * @param y y coordinate of the location to draw this relative to the parent matrix's origin. + * @param mName name to draw to the right of the icon. Leave blank to omit. + */ + KaleidoEntryIconFlag(const char* iconResourceName, int iconFormat, int iconSize, int iconWidth, int iconHeight, + Color_RGBA8 iconColor, FlagType flagType, int flag, int16_t x, int16_t y, + std::string name = ""); + void Update(PlayState* play) override; +private: + FlagType mFlagType; + int mFlag; +}; + +/** + * KaleidoEntryIcon subclass to render a count of how many collectibles have been collected. + * The `required` and `total` values can be omitted from the constructor or set to 0 to only + * render the count and not show progress towards a required amount or a total. + */ +class KaleidoEntryIconCountRequired : public KaleidoEntryIcon { +public: + /** + * @param iconResourceName resource name of the icon to draw + * @param iconFormat flag representing the format of the icon (i.e. G_IM_FMT_IA) + * @param iconSize flag representing the size of the icon in bytes (i.e. G_IM_SIZ_8b) + * @param iconWidth pixel width of the source icon image + * @param iconHeight pixel height of the source icon image + * @param iconColor Color to shade the icon with. This may be ignored for certain icon formats + * @param flagType FlagType of the flag to check for (i.e. FlagType::FLAG_RANDOMIZER_INF + * @param flag flag to check for. An integer can be provided but enum values should be preferred + * @param x x coordinate of the location to draw this relative to the parent matrix's origin. + * @param y y coordinate of the location to draw this relative to the parent matrix's origin. + * @param watch a pointer to an integer value to watch. Update will check this value to update + * a local count variable. + * @param required The amount of this collectible required to beat the seed. Set to 0 to not render. + * @param total The amount of this collectible available in the seed. Set to 0 to not render. + */ + KaleidoEntryIconCountRequired(const char* iconResourceName, int iconFormat, int iconSize, int iconWidth, int iconHeight, + Color_RGBA8 iconColor, int16_t x, int16_t y, int* watch, int required = 0, int total = 0); + void Update(PlayState* play) override; +private: + int* mWatch; + int mRequired; + int mTotal; + int mCount; + + void BuildText(); +}; + +class KaleidoEntryOcarinaButtons : public KaleidoEntryIcon { +public: + KaleidoEntryOcarinaButtons(int16_t x, int16_t y); + void Update(PlayState* play) override; + void Draw(PlayState* play, std::vector* mEntryDl) override; +private: + void CalculateColors(); + + std::array mButtonColors = {}; + std::array mButtonCollected = {}; +}; + +class Kaleido { +public: + Kaleido(); + void Draw(PlayState* play); + void Update(PlayState* play); +private: + std::vector> mEntries; + std::vector mEntryDl; + int mTopIndex = 0; + int mNumVisible = 7; +}; +} // Rando + +extern "C" { +#endif +void RandoKaleido_DrawMiscCollectibles(PlayState* play); +void RandoKaleido_UpdateMiscCollectibles(int16_t inDungeonScene); +#ifdef __cplusplus +} +#endif +void RandoKaleido_RegisterHooks(); + + +#endif //KALEIDO_H diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index a8141ca81..2c0368fea 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -35,6 +35,7 @@ #include "src/overlays/actors/ovl_En_Door/z_en_door.h" #include "objects/object_link_boy/object_link_boy.h" #include "objects/object_link_child/object_link_child.h" +#include "kaleido.h" extern "C" { #include @@ -1676,4 +1677,5 @@ void InitMods() { RegisterHurtContainerModeHandler(); RegisterPauseMenuHooks(); RegisterSkeletonKey(); + RandoKaleido_RegisterHooks(); } diff --git a/soh/soh/Enhancements/randomizer/context.cpp b/soh/soh/Enhancements/randomizer/context.cpp index 18f187c4d..29e576580 100644 --- a/soh/soh/Enhancements/randomizer/context.cpp +++ b/soh/soh/Enhancements/randomizer/context.cpp @@ -11,6 +11,7 @@ #include "fishsanity.h" #include "macros.h" #include "3drando/hints.hpp" +#include "../kaleido.h" #include #include @@ -431,4 +432,10 @@ TrickOption& Context::GetTrickOption(const RandomizerTrick key) const { return mSettings->GetTrickOption(key); } +std::shared_ptr Context::GetKaleido() { + if (mKaleido == nullptr) { + mKaleido = std::make_shared(); + } + return mKaleido; +} } // namespace Rando diff --git a/soh/soh/Enhancements/randomizer/context.h b/soh/soh/Enhancements/randomizer/context.h index a1a7d0d54..842b73c25 100644 --- a/soh/soh/Enhancements/randomizer/context.h +++ b/soh/soh/Enhancements/randomizer/context.h @@ -30,6 +30,7 @@ class Dungeons; class DungeonInfo; class TrialInfo; class Trials; +class Kaleido; class Context { public: @@ -70,6 +71,7 @@ class Context { DungeonInfo* GetDungeon(size_t key) const; std::shared_ptr GetLogic(); std::shared_ptr GetTrials(); + std::shared_ptr GetKaleido(); TrialInfo* GetTrial(size_t key) const; TrialInfo* GetTrial(TrialKey key) const; static Sprite* GetSeedTexture(uint8_t index); @@ -101,6 +103,7 @@ class Context { std::shared_ptr mLogic; std::shared_ptr mTrials; std::shared_ptr mFishsanity; + std::shared_ptr mKaleido; bool mSeedGenerated = false; bool mSpoilerLoaded = false; bool mPlandoLoaded = false; diff --git a/soh/src/code/z_kaleido_setup.c b/soh/src/code/z_kaleido_setup.c index 3efbd6ade..69ea3030f 100644 --- a/soh/src/code/z_kaleido_setup.c +++ b/soh/src/code/z_kaleido_setup.c @@ -133,6 +133,8 @@ void KaleidoSetup_Init(PlayState* play) { pauseCtx->ocarinaSongIdx = -1; pauseCtx->cursorSpecialPos = 0; + pauseCtx->randoQuestMode = 0; + View_Init(&pauseCtx->view, play->state.gfxCtx); } diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c index a8b7536f0..68ace2c39 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c @@ -20,6 +20,7 @@ #include "soh/Enhancements/randomizer/randomizer_grotto.h" #include "soh/Enhancements/cosmetics/cosmeticsTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/kaleido.h" static void* sEquipmentFRATexs[] = { @@ -1419,10 +1420,15 @@ void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) { gSPMatrix(POLY_KAL_DISP++, MATRIX_NEWMTX(gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); - POLY_KAL_DISP = KaleidoScope_DrawPageSections(POLY_KAL_DISP, pauseCtx->questPageVtx, - sQuestStatusTexs[gSaveContext.language]); - - KaleidoScope_DrawQuestStatus(play, gfxCtx); + if (pauseCtx->randoQuestMode) { + POLY_KAL_DISP = KaleidoScope_DrawPageSections(POLY_KAL_DISP, pauseCtx->saveVtx, + sSaveTexs[gSaveContext.language]); + RandoKaleido_DrawMiscCollectibles(play); + } else { + POLY_KAL_DISP = KaleidoScope_DrawPageSections(POLY_KAL_DISP, pauseCtx->questPageVtx, + sQuestStatusTexs[gSaveContext.language]); + KaleidoScope_DrawQuestStatus(play, gfxCtx); + } } if (pauseCtx->pageIndex != PAUSE_MAP) { @@ -1514,10 +1520,15 @@ void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) { gSPMatrix(POLY_KAL_DISP++, MATRIX_NEWMTX(gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); - POLY_KAL_DISP = KaleidoScope_DrawPageSections(POLY_KAL_DISP, pauseCtx->questPageVtx, - sQuestStatusTexs[gSaveContext.language]); - - KaleidoScope_DrawQuestStatus(play, gfxCtx); + if (pauseCtx->randoQuestMode) { + POLY_KAL_DISP = KaleidoScope_DrawPageSections(POLY_KAL_DISP, pauseCtx->saveVtx, + sSaveTexs[gSaveContext.language]); + RandoKaleido_DrawMiscCollectibles(play); + } else { + POLY_KAL_DISP = KaleidoScope_DrawPageSections(POLY_KAL_DISP, pauseCtx->questPageVtx, + sQuestStatusTexs[gSaveContext.language]); + KaleidoScope_DrawQuestStatus(play, gfxCtx); + } if (pauseCtx->cursorSpecialPos == 0) { KaleidoScope_DrawCursor(play, PAUSE_QUEST); @@ -4015,6 +4026,9 @@ void KaleidoScope_Update(PlayState* play) Interface_ChangeAlpha(50); pauseCtx->unk_1EC = 0; pauseCtx->state = 7; + } else if (CHECK_BTN_ALL(input->press.button, BTN_CUP) && pauseCtx->pageIndex == PAUSE_QUEST) { + Audio_PlaySoundGeneral(NA_SE_SY_DECIDE, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); + pauseCtx->randoQuestMode ^= 1; } break;