Files
Shipwright/soh/soh/Enhancements/kaleido.cpp
T
2026-08-25 08:02:02 +00:00

610 lines
30 KiB
C++

#include <sstream>
#include "ship/utils/StringHelper.h"
#include <libultraship/bridge/consolevariablebridge.h>
#include "kaleido.h"
#include "soh/Enhancements/randomizer/SeedContext.h"
#include "soh/ShipInit.hpp"
#include "soh/ShipUtils.h"
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/cosmetics/cosmeticsTypes.h"
extern "C" {
#include "objects/gameplay_keep/gameplay_keep.h"
#include "z64.h"
#include "functions.h"
#include "macros.h"
#include "variables.h"
#include <textures/parameter_static/parameter_static.h>
#include "soh_assets.h"
#include "textures/icon_item_static/icon_item_static.h"
extern PlayState* gPlayState;
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));
} else if (mIconSize == G_IM_SIZ_16b) {
Gfx iconTexture[] = { gsDPLoadTextureBlock(
mIconResourceName, G_IM_FMT_RGBA, G_IM_SIZ_16b, 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(std::string text) : mText(std::move(text)) {
mHeight = 0;
mWidth = 0;
vtx = nullptr;
}
void KaleidoEntry::SetYOffset(int yOffset) {
mY = yOffset;
}
void KaleidoEntry::SetSelected(bool val) {
mSelected = val;
}
void KaleidoEntryIcon::Draw(PlayState* play, std::vector<Gfx>* mEntryDl) {
PauseContext* pauseCtx = &play->pauseCtx;
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));
// cursor (if selected)
if (mSelected) {
mEntryDl->push_back(gsDPSetPrimColor(0, 0, 255, 255, 255, 255));
mEntryDl->push_back(gsSPVertex(vtx, 4, 0));
Gfx cursorIconTex[] = { gsDPLoadTextureBlock(gArrowCursorTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 24, 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(cursorIconTex), std::end(cursorIconTex));
mEntryDl->push_back(gsSP1Quadrangle(0, 2, 3, 1, 0));
}
// 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], 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++) {
// 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 8 vertices have already been loaded for the preceding icon and cursor.
if (i % 16 == 0) {
size_t numVtxToLoad = std::min<size_t>(numChar - i, 16) * 4;
mEntryDl->push_back(gsSPVertex(&vtx[8 + (vtxGroup * 16 * 4)], numVtxToLoad, 0));
vtxGroup++;
}
auto texture = reinterpret_cast<uintptr_t>(Ship_GetCharFontTexture(mText[i]));
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 = 0;
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), "Greg"));
if (ctx->GetOption(RSK_SHUFFLE_FISHING_POLE)) {
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(
gItemIconFishingPoleTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 },
FlagType::FLAG_RANDOMIZER_INF, static_cast<int>(RAND_INF_FISHING_POLE_FOUND), "Fishing Pole"));
}
if (ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get() > 0) {
mEntries.push_back(std::make_shared<KaleidoEntryIconCountRequired>(
gTriforcePieceTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 },
reinterpret_cast<int*>(&gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected),
ctx->GetOption(RSK_WINCON_TRIFORCE_COUNT).Get(), ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get()));
}
if (ctx->GetOption(RSK_SKELETON_KEY)) {
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(
gSmallKeyCounterIconTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16, Color_RGBA8{ 255, 255, 255, 255 },
FlagType::FLAG_RANDOMIZER_INF, static_cast<int>(RAND_INF_HAS_SKELETON_KEY), "Skeleton Key"));
}
if (ctx->GetOption(RSK_SHUFFLE_OCARINA_BUTTONS)) {
mEntries.push_back(std::make_shared<KaleidoEntryOcarinaButtons>());
}
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, bossSoulNames[i - RAND_INF_GOHMA_SOUL]));
}
}
if (ctx->GetOption(RSK_GANONS_SOUL).IsNot(RO_GANONS_SOUL_STARTWITH)) {
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, "Ganon's Soul"));
}
if (ctx->GetOption(RSK_LOCK_OVERWORLD_DOORS)) {
int rg = RG_GUARD_HOUSE_KEY;
for (int i = RAND_INF_GUARD_HOUSE_KEY_OBTAINED; i <= RAND_INF_FISHING_HOLE_KEY_OBTAINED; i += 2, rg++) {
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(
gSmallKeyCounterIconTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16, Color_RGBA8{ 255, 255, 255, 255 },
FlagType::FLAG_RANDOMIZER_INF, i,
Rando::StaticData::RetrieveItem(static_cast<RandomizerGet>(rg)).GetName().english));
}
}
// Default A button color stored for action shuffle (crawl, climb, etc.)
// TODO: find a way to update this so we can match Cosmetics Editor color, or just replace these icons
Color_RGBA8 aButtonColor = { 90, 90, 255, 255 };
if (ctx->GetOption(RSK_SHUFFLE_CRAWL)) {
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(gButtonBackgroundTex, G_IM_FMT_IA, G_IM_SIZ_8b, 32,
32, aButtonColor, FlagType::FLAG_RANDOMIZER_INF,
RAND_INF_CAN_CRAWL, "Crawl"));
}
if (ctx->GetOption(RSK_SHUFFLE_CLIMB)) {
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(gButtonBackgroundTex, G_IM_FMT_IA, G_IM_SIZ_8b, 32,
32, aButtonColor, FlagType::FLAG_RANDOMIZER_INF,
RAND_INF_CAN_CLIMB, "Climb"));
}
if (ctx->GetOption(RSK_SHUFFLE_GRAB)) {
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(gButtonBackgroundTex, G_IM_FMT_IA, G_IM_SIZ_8b, 32,
32, aButtonColor, FlagType::FLAG_RANDOMIZER_INF,
RAND_INF_CAN_GRAB, "Grab"));
}
if (ctx->GetOption(RSK_SHUFFLE_SPEAK)) {
int rg = RG_SPEAK_DEKU;
for (int i = RAND_INF_CAN_SPEAK_DEKU; i <= RAND_INF_CAN_SPEAK_ZORA; i++, rg++) {
std::string speakName = Rando::StaticData::RetrieveItem(static_cast<RandomizerGet>(rg)).GetName().english;
StringHelper::ReplaceOriginal(speakName, " Jabber Nut", "");
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(gItemIconDekuNutTex, G_IM_FMT_RGBA, G_IM_SIZ_32b,
32, 32, Color_RGBA8{ 255, 255, 255, 255 },
FlagType::FLAG_RANDOMIZER_INF, i, speakName));
}
}
if (ctx->GetOption(RSK_SHUFFLE_OPEN_CHEST)) {
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(
gMapChestIconTex, G_IM_FMT_RGBA, G_IM_SIZ_16b, 8, 8, Color_RGBA8{ 255, 255, 255, 255 },
FlagType::FLAG_RANDOMIZER_INF, RAND_INF_CAN_OPEN_CHEST, "Open Chests"));
if (ctx->GetOption(RSK_SHUFFLE_OPEN_CHEST).Is(RO_OPEN_CHEST_PROGRESSIVE)) {
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(
gMapChestIconTex, G_IM_FMT_RGBA, G_IM_SIZ_16b, 8, 8, Color_RGBA8{ 255, 255, 255, 255 },
FlagType::FLAG_RANDOMIZER_INF, RAND_INF_CAN_OPEN_LARGE_CHEST, "Open Large Chests"));
}
}
if (ctx->GetOption(RSK_SHUFFLE_SWIM)) {
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(
gItemIconScaleSilverTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 },
FlagType::FLAG_RANDOMIZER_INF, RAND_INF_CAN_SWIM, "Swim"));
}
if (ctx->GetOption(RSK_SHUFFLE_BEAN_SOULS)) {
int rg = RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL;
for (int i = RAND_INF_DEATH_MOUNTAIN_CRATER_BEAN_SOUL; i <= RAND_INF_ZORAS_RIVER_BEAN_SOUL; i++, rg++) {
std::string beanSoulName =
Rando::StaticData::RetrieveItem(static_cast<RandomizerGet>(rg)).GetName().english;
StringHelper::ReplaceOriginal(beanSoulName, " Bean Soul", "");
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(
gItemIconMagicBeanTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 },
FlagType::FLAG_RANDOMIZER_INF, i, std::move(beanSoulName)));
}
}
if (ctx->GetOption(RSK_ROCS_FEATHER)) {
mEntries.push_back(std::make_shared<KaleidoEntryIconFlag>(
gRocsFeatherTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 },
FlagType::FLAG_RANDOMIZER_INF, RAND_INF_OBTAINED_ROCS_FEATHER, "Roc's Feather"));
}
}
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_39Opa(play->state.gfxCtx);
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);
if (((pauseCtx->unk_1E4 == 0) || (pauseCtx->unk_1E4 == 5) || (pauseCtx->unk_1E4 == 8)) &&
(pauseCtx->pageIndex == PAUSE_QUEST)) {
u16 buttonsToCheck = BTN_CLEFT | BTN_CRIGHT;
if (CVarGetInteger(CVAR_SETTING("DPadOnPause"), 0)) {
buttonsToCheck |= BTN_DUP | BTN_DDOWN | BTN_DLEFT | BTN_DRIGHT;
}
if (!((pauseCtx->state != 6) || ((pauseCtx->stickRelX == 0) && (pauseCtx->stickRelY == 0) &&
!CHECK_BTN_ANY(input->press.button, buttonsToCheck)))) {
if (pauseCtx->cursorSpecialPos == 0) {
if ((pauseCtx->stickRelY > 30) || CHECK_BTN_ALL(input->press.button, BTN_DUP)) {
if (mCursorPos > 0) {
mCursorPos--;
Audio_PlaySfxGeneral(NA_SE_SY_CURSOR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
}
if (mCursorPos < mTopIndex) {
mTopIndex = mCursorPos;
}
} else if ((pauseCtx->stickRelY < -30) || CHECK_BTN_ALL(input->press.button, BTN_DDOWN)) {
if (mCursorPos < static_cast<int>(mEntries.size() - 1)) {
mCursorPos++;
Audio_PlaySfxGeneral(NA_SE_SY_CURSOR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
}
if (mCursorPos >= mTopIndex + mNumVisible &&
mTopIndex + mNumVisible < static_cast<int>(mEntries.size())) {
mTopIndex = mCursorPos - mNumVisible + 1;
}
} else if (CHECK_BTN_ALL(input->press.button, BTN_CLEFT)) {
if (mCursorPos > 0) {
mCursorPos -= mNumVisible;
if (mCursorPos < 0) {
mCursorPos = 0;
}
Audio_PlaySfxGeneral(NA_SE_SY_CURSOR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
}
if (mCursorPos < mTopIndex) {
mTopIndex = mCursorPos;
}
} else if (CHECK_BTN_ALL(input->press.button, BTN_CRIGHT)) {
if (mCursorPos < static_cast<int>(mEntries.size() - 1)) {
mCursorPos += mNumVisible;
if (mCursorPos > static_cast<int>(mEntries.size() - 1)) {
mCursorPos = static_cast<int>(mEntries.size() - 1);
}
Audio_PlaySfxGeneral(NA_SE_SY_CURSOR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
}
if (mCursorPos >= mTopIndex + mNumVisible &&
mTopIndex + mNumVisible < static_cast<int>(mEntries.size())) {
mTopIndex = mCursorPos - mNumVisible + 1;
}
} else if ((pauseCtx->stickRelX < -30) || CHECK_BTN_ALL(input->press.button, BTN_DLEFT)) {
KaleidoScope_MoveCursorToSpecialPos(play, PAUSE_CURSOR_PAGE_LEFT);
pauseCtx->unk_1E4 = 0;
} else if ((pauseCtx->stickRelX > 30) || 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) || CHECK_BTN_ALL(input->press.button, BTN_DRIGHT)) {
pauseCtx->cursorSpecialPos = 0;
Audio_PlaySfxGeneral(NA_SE_SY_CURSOR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
}
} else if (pauseCtx->cursorSpecialPos == PAUSE_CURSOR_PAGE_RIGHT) {
if ((pauseCtx->stickRelX < -30) || CHECK_BTN_ALL(input->press.button, BTN_DLEFT)) {
pauseCtx->cursorSpecialPos = 0;
Audio_PlaySfxGeneral(NA_SE_SY_CURSOR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
}
}
} else if (pauseCtx->cursorSpecialPos != 0 && pauseCtx->state == 7) {
pauseCtx->cursorSpecialPos = 0;
}
}
int yOffset = 1;
for (int i = mTopIndex; i < (mTopIndex + mNumVisible) && i < static_cast<int>(mEntries.size()); i++) {
auto& entry = mEntries[i];
entry->SetYOffset(yOffset);
yOffset += 9;
Matrix_Push();
entry->SetSelected((i == mCursorPos) && !(pauseCtx->cursorSpecialPos == PAUSE_CURSOR_PAGE_RIGHT ||
pauseCtx->cursorSpecialPos == PAUSE_CURSOR_PAGE_LEFT));
entry->Draw(play, &mEntryDl);
Matrix_Pop();
}
mEntryDl.push_back(gsSPEndDisplayList());
gSPDisplayList(POLY_OPA_DISP++, mEntryDl.data());
CLOSE_DISPS(play->state.gfxCtx);
}
void Kaleido::Update(PlayState* play) {
for (int i = mTopIndex; i < (mTopIndex + mNumVisible) && i < static_cast<int>(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,
std::string name)
: KaleidoEntryIcon(iconResourceName, iconFormat, iconSize, iconWidth, iconHeight, iconColor, std::move(name)),
mFlagType(flagType), mFlag(flag) {
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,
int* watch, int required, int total)
: KaleidoEntryIcon(iconResourceName, iconFormat, iconSize, iconWidth, iconHeight, iconColor), mWatch(watch),
mRequired(required), mTotal(total) {
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, plus one for the cursor.
Vtx* vertices = (Vtx*)calloc(sizeof(Vtx[4]), mText.length() + 2);
// Vertex for the cursor.
Ship_CreateQuadVertexGroup(vertices, offsetX, offsetY, 16, 24, 0);
offsetX += 18;
// Vertex for the preceding icon.
Ship_CreateQuadVertexGroup(&vertices[4], offsetX, offsetY, mIconWidth, mIconHeight, 0);
offsetX += 18;
for (size_t i = 0; i < mText.length(); i++) {
int charWidth = static_cast<int>(Ship_GetCharFontWidth(mText[i]));
Ship_CreateQuadVertexGroup(&(vertices)[((i + 1) * 4) + 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] = 15; // top-right x
vertices[2].v.ob[1] = 15; // bottom-left y
vertices[3].v.ob[0] = 15; // bottom-right x
vertices[3].v.ob[1] = 15; // bottom-right y
vertices[5].v.ob[0] = 32; // top-right x
vertices[6].v.ob[1] = 16; // bottom-left-y
vertices[7].v.ob[0] = 32; // bottom-right x
vertices[7].v.ob[1] = 16; // bottom-right y
for (size_t i = 0; i < mText.length() + 2; i++) {
size_t j = i * 4;
vertices[j].v.ob[0] = vertices[j].v.ob[0] / 2;
vertices[j].v.ob[1] = vertices[j].v.ob[1] / 2;
vertices[j + 1].v.ob[0] = vertices[j + 1].v.ob[0] / 2;
vertices[j + 1].v.ob[1] = vertices[j + 1].v.ob[1] / 2;
vertices[j + 2].v.ob[0] = vertices[j + 2].v.ob[0] / 2;
vertices[j + 2].v.ob[1] = vertices[j + 2].v.ob[1] / 2;
vertices[j + 3].v.ob[0] = vertices[j + 3].v.ob[0] / 2;
vertices[j + 3].v.ob[1] = vertices[j + 3].v.ob[1] / 2;
}
mWidth = static_cast<int16_t>(offsetX / 2);
mHeight = static_cast<int16_t>(8);
vtx = vertices;
}
KaleidoEntryIcon::KaleidoEntryIcon(const char* iconResourceName, int iconFormat, int iconSize, int iconWidth,
int iconHeight, Color_RGBA8 iconColor, std::string text)
: KaleidoEntry(std::move(text)), mIconResourceName(iconResourceName), mIconFormat(iconFormat), mIconSize(iconSize),
mIconWidth(iconWidth), mIconHeight(iconHeight), mIconColor(iconColor) {
}
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()
: KaleidoEntryIcon(gItemIconOcarinaOfTimeTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32,
Color_RGBA8{ 255, 255, 255, 255 }, "\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);
mButtonCollected[1] = GameInteractor::RawAction::CheckFlag(FLAG_RANDOMIZER_INF, RAND_INF_HAS_OCARINA_C_UP);
mButtonCollected[2] = GameInteractor::RawAction::CheckFlag(FLAG_RANDOMIZER_INF, RAND_INF_HAS_OCARINA_C_DOWN);
mButtonCollected[3] = GameInteractor::RawAction::CheckFlag(FLAG_RANDOMIZER_INF, RAND_INF_HAS_OCARINA_C_LEFT);
mButtonCollected[4] = GameInteractor::RawAction::CheckFlag(FLAG_RANDOMIZER_INF, RAND_INF_HAS_OCARINA_C_RIGHT);
CalculateColors();
mAchieved = false;
for (size_t 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));
// cursor (if selected)
if (mSelected) {
mEntryDl->push_back(gsDPSetPrimColor(0, 0, 255, 255, 255, 255));
mEntryDl->push_back(gsSPVertex(vtx, 4, 0));
Gfx cursorIconTex[] = { gsDPLoadTextureBlock(gArrowCursorTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 24, 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(cursorIconTex), std::end(cursorIconTex));
mEntryDl->push_back(gsSP1Quadrangle(0, 2, 3, 1, 0));
}
// 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], 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));
// 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[8 + (vtxGroup * 16 * 4)], numVtxToLoad, 0));
vtxGroup++;
}
auto texture = reinterpret_cast<uintptr_t>(Ship_GetCharFontTexture(mText[i]));
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));
}
} // namespace Rando
static void RandoKaleido_RegisterHooks() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnKaleidoscopeUpdate>(
RandoKaleido_UpdateMiscCollectibles);
}
static RegisterShipInitFunc initFunc(RandoKaleido_RegisterHooks);