Kaleido Menu for Miscellaneous Collectibles (#3852)

* Adds in-game display of certain rando collectibles.

Before, these were only available through the Item Tracker ImGui Window. With this commit, they can be accessed via holding C-Up on the Map Screen. Currently I've added Greg and Triforce Pieces (when applicable) to this menu. Boss Souls, Ocarina Buttons, and eventually Silver Rupees will be added later.

* Adds ocarina buttons to in-game display

* Initial pass on rendering the text on a black background.

* Starting to render boss soul icons

* Better alignment and rendering of Boss Soul icons.

* Adds icons prefixing the list entries.

* Switches boss souls to 32x32 icon.

* Partially working Matrix/Vtx implementation

Currently hardcoded Greg text, replacing map screen completely.

* Now rendering properly thanks to Archez!

* Better implementation of accessing the new page.

- now attached to Quest status instead of Map
- now triggered by a toggle instead of holding a button
- now has its own background (temporarily save screen but will be replaced with something custom later)

* Make KaleidoEntry's reactive to game state

Adds Greg proper and Triforce Hunt to the Misc. Collectibles Page.

* Conditionally render Triforce Hunt

* Documentation/Cleanup

* WIP Ocarina Buttons rendering

* Working ocarina buttons display

* Renders buttons as Gray instead of using Grayscale

This may seem inconsistent, but with Grayscale they technically render as different shades of gray, especially with custom cosmetics. With this they now render as the same shade of gray.

* Makes Ocarina Icon gray when no buttons have been collected.

* Adds Boss Souls.

Currently they run off the menu, need to implement scrolling.

* Implement Scrolling for the menu.

Need to figure out how to throttle the stick inputs still.

* Moves input handling to draw function.

I hate it but that's how Kaleido does it and there's some input throttling logic in there, so in order to make this feel like a kaleido menu I have to also handle input in the draw function.

* Removes custom cosmetic handling of Ocarina Buttons.

I've chosen not to respect the cosmetics for the sake of accessibility
and color contrast, but the code is still present and commented out in
case we want to reverse that decision.

* Hopefully fixes mac build errors.

* Implements update function via Hook.

* Another mac fix hopefully

* Cleans up unused code from the rectangle based attempt.

* Clean up more unused code

* Commit Boss Soul icon

* Fix typo

* Remove commented code

* Improve toggle functionality

* Re-introduce cosmetic matching for ocarina buttons

* Revert some unnecessary formatting changes

* Fix cursor/page turning issue

More improvements to come here (drawing arrows, custom text at the bottom, etc.)

* Fix some more formatting changes

* One last batch of formatting reverts
This commit is contained in:
Christopher Leggett 2024-10-06 20:58:45 -04:00 committed by GitHub
parent bd142a0eed
commit bcf9f392f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 702 additions and 9 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -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<RandomizerInf>(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:

View File

@ -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 <textures/message_static/message_static.h>
#include <textures/parameter_static/parameter_static.h>
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 <sstream>
extern "C" {
void KaleidoScope_MoveCursorToSpecialPos(PlayState* play, u16 specialPos);
}
namespace Rando {
void KaleidoEntryIcon::LoadIconTex(std::vector<Gfx>* 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<Gfx>* 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<size_t>(numChar - i, 16) * 4;
mEntryDl->push_back(gsSPVertex(&vtx[4 + (vtxGroup * 16 * 4)], numVtxToLoad, 0));
vtxGroup++;
}
if (texIndex != 0) {
auto texture = reinterpret_cast<uintptr_t>(Font_FetchCharTexture(texIndex));
auto vertexStart = static_cast<int16_t>(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<KaleidoEntryIconFlag>(gRupeeCounterIconTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16,
Color_RGBA8{ 0xC8, 0xFF, 0x64, 255 }, FlagType::FLAG_RANDOMIZER_INF,
static_cast<int>(RAND_INF_GREG_FOUND), 0, yOffset, "Greg"));
yOffset += 18;
if (ctx->GetOption(RSK_TRIFORCE_HUNT)) {
mEntries.push_back(
std::make_shared<KaleidoEntryIconCountRequired>(
gTriforcePieceTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255,255,255,255 }, 0,
yOffset, reinterpret_cast<int*>(&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<KaleidoEntryOcarinaButtons>(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<KaleidoEntryIconFlag>(
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<KaleidoEntryIconFlag>(
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<int16_t>(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<uint16_t>(mText[i] - 32);
int charWidth = 0;
if (charIndex >= 0) {
charWidth = static_cast<int>(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<int16_t>(offsetX);
mHeight = static_cast<int16_t>(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<Gfx>* 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<size_t>(numChar - i, 16) * 4;
mEntryDl->push_back(gsSPVertex(&vtx[4 + (vtxGroup * 16 * 4)], numVtxToLoad, 0));
vtxGroup++;
}
if (texIndex != 0) {
auto texture = reinterpret_cast<uintptr_t>(Font_FetchCharTexture(texIndex));
auto vertexStart = static_cast<int16_t>(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<GameInteractor::OnKaleidoscopeUpdate>(RandoKaleido_UpdateMiscCollectibles);
}

View File

@ -0,0 +1,176 @@
#ifndef KALEIDO_H
#define KALEIDO_H
#include <z64.h>
#ifdef __cplusplus
#include <vector>
#include <string>
#include <array>
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<Gfx>* 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<Gfx>* 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<Gfx>* 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<Gfx>* mEntryDl) override;
private:
void CalculateColors();
std::array<Color_RGBA8, 5> mButtonColors = {};
std::array<bool, 5> mButtonCollected = {};
};
class Kaleido {
public:
Kaleido();
void Draw(PlayState* play);
void Update(PlayState* play);
private:
std::vector<std::shared_ptr<KaleidoEntry>> mEntries;
std::vector<Gfx> 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

View File

@ -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 <z64.h>
@ -1676,4 +1677,5 @@ void InitMods() {
RegisterHurtContainerModeHandler();
RegisterPauseMenuHooks();
RegisterSkeletonKey();
RandoKaleido_RegisterHooks();
}

View File

@ -11,6 +11,7 @@
#include "fishsanity.h"
#include "macros.h"
#include "3drando/hints.hpp"
#include "../kaleido.h"
#include <fstream>
#include <spdlog/spdlog.h>
@ -431,4 +432,10 @@ TrickOption& Context::GetTrickOption(const RandomizerTrick key) const {
return mSettings->GetTrickOption(key);
}
std::shared_ptr<Kaleido> Context::GetKaleido() {
if (mKaleido == nullptr) {
mKaleido = std::make_shared<Kaleido>();
}
return mKaleido;
}
} // namespace Rando

View File

@ -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<Logic> GetLogic();
std::shared_ptr<Trials> GetTrials();
std::shared_ptr<Kaleido> 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<Logic> mLogic;
std::shared_ptr<Trials> mTrials;
std::shared_ptr<Fishsanity> mFishsanity;
std::shared_ptr<Kaleido> mKaleido;
bool mSeedGenerated = false;
bool mSpoilerLoaded = false;
bool mPlandoLoaded = false;

View File

@ -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);
}

View File

@ -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;