Feature/show cartridge icons in initial transferpak scene (#4)

We now show a cartridge icon in the initial transferpak scene, which switches to the cartridge that was detected.
This commit is contained in:
Philippe Symons 2024-08-23 12:32:12 +02:00 committed by GitHub
parent 6209da042d
commit 8e556391f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 221 additions and 19 deletions

BIN
assets/cartridge-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -58,6 +58,16 @@ typedef struct SpriteRenderSettings
* @brief Rotation angle in radians
*/
float rotationAngle;
struct {
/**
* WARNING: needs to be a multiple of 8
*/
uint8_t numColors;
/**
* @brief If set, this RGBA16 array will replace the original color palette for the specified sprite
*/
const uint16_t* colorsRGBA16;
} customPalette;
} SpriteRenderSettings;
typedef struct SurfaceRenderSettings

View File

@ -37,6 +37,7 @@ private:
TransferPakDetectionWidget tpakDetectWidget_;
WidgetFocusChainSegment tpakDetectWidgetSegment_;
DialogData diagData_;
TextRenderSettings pokeMe64TextSettings_;
char playerName_[PLAYER_NAME_SIZE];
const char* gameTypeString_;
};

View File

@ -54,7 +54,7 @@ private:
TextRenderSettings smallTextSettings_;
TextRenderSettings statsSettings_;
Rectangle spriteBounds_;
char nameBuffer_[15];
char nameBuffer_[25];
char levelAndNumberBuffer_[40];
char pokeStatsString_[150];
char otInfoString_[40];

View File

@ -130,17 +130,22 @@ private:
*/
bool detectGameType();
void updateCartridgeIcon();
TransferPakDetectionWidgetStyle style_;
AnimationManager& animManager_;
TransferPakManager& tpakManager_;
Rectangle bounds_;
Rectangle textBounds_;
TransferPakWidgetState currentState_;
joypad_inputs_t previousInputState_;
Gen1GameType gen1Type_;
Gen2GameType gen2Type_;
void (*stateChangedCallback_)(void*, TransferPakWidgetState);
void* stateChangedCallbackContext_;
sprite_t* cartridgeIconSprite_;
sprite_t* cartridgeLabelSprite_;
SpriteRenderSettings cartridgeIconRenderSettings_;
SpriteRenderSettings cartridgeLabelRenderSettings_;
bool focused_;
bool visible_;
};

View File

@ -1,6 +1,47 @@
#include "core/RDPQGraphics.h"
#include "core/Sprite.h"
/**
* @brief Custom version of sprite_upload_palette() from rdpq_sprite.c
* This variant allows you to override the sprites' palette with the SpriteRenderSettings instead of using the one
* embedded in the sprite itself
*/
static void custom_sprite_upload_palette(sprite_t *sprite, const SpriteRenderSettings& renderSettings)
{
// Check if the sprite has a palette
tex_format_t fmt = sprite_get_format(sprite);
rdpq_tlut_t tlut_mode = rdpq_tlut_from_format(fmt);
// Configure the TLUT render mode
rdpq_mode_tlut(tlut_mode);
if (tlut_mode != TLUT_NONE) {
// Load the palette (if any). We account for sprites being CI4
// but without embedded palette: mksprite doesn't create sprites like
// this today, but it could in the future (eg: sharing a palette across
// multiple sprites).
if(renderSettings.customPalette.numColors)
{
rdpq_tex_upload_tlut(const_cast<uint16_t*>(renderSettings.customPalette.colorsRGBA16), 0, renderSettings.customPalette.numColors);
}
else
{
uint16_t *pal = sprite_get_palette(sprite);
if (pal) rdpq_tex_upload_tlut(pal, 0, fmt == FMT_CI4 ? 16 : 256);
}
}
}
/**
* @brief Custom version of rdpq_sprite_blit which allows you to call custom_sprite_upload_palette separately
*/
static void custom_rdpq_sprite_blit(sprite_t* sprite, float x0, float y0, const rdpq_blitparms_t *parms)
{
// Get the sprite surface
surface_t surf = sprite_get_pixels(sprite);
rdpq_tex_blit(&surf, x0, y0, parms);
}
static void render_sprite_normal(const Rectangle &dstRect, sprite_t *sprite, const SpriteRenderSettings &renderSettings)
{
if (!isZeroSizeRectangle(renderSettings.srcRect))
@ -15,7 +56,8 @@ static void render_sprite_normal(const Rectangle &dstRect, sprite_t *sprite, con
.theta = renderSettings.rotationAngle
};
rdpq_sprite_blit(sprite, dstRect.x, dstRect.y, &blitParams);
custom_sprite_upload_palette(sprite, renderSettings);
custom_rdpq_sprite_blit(sprite, dstRect.x, dstRect.y, &blitParams);
}
else
{
@ -25,7 +67,8 @@ static void render_sprite_normal(const Rectangle &dstRect, sprite_t *sprite, con
.theta = renderSettings.rotationAngle
};
rdpq_sprite_blit(sprite, dstRect.x, dstRect.y, &blitParams);
custom_sprite_upload_palette(sprite, renderSettings);
custom_rdpq_sprite_blit(sprite, dstRect.x, dstRect.y, &blitParams);
}
}
@ -44,7 +87,7 @@ static void render_sprite_ninegrid(const Rectangle &dstRect, sprite_t *sprite, c
.width = renderSettings.srcRect.x,
.height = renderSettings.srcRect.y
};
render_sprite_normal(curDest, sprite, {.renderMode = SpriteRenderMode::NORMAL, .srcRect = curSrc});
render_sprite_normal(curDest, sprite, {.renderMode = SpriteRenderMode::NORMAL, .srcRect = curSrc, .customPalette = renderSettings.customPalette});
// top edge
curDest = {
@ -59,7 +102,7 @@ static void render_sprite_ninegrid(const Rectangle &dstRect, sprite_t *sprite, c
.width = sprite->width - renderSettings.srcRect.width - renderSettings.srcRect.x,
.height = renderSettings.srcRect.y
};
render_sprite_normal(curDest, sprite, {.renderMode = SpriteRenderMode::NORMAL, .srcRect = curSrc});
render_sprite_normal(curDest, sprite, {.renderMode = SpriteRenderMode::NORMAL, .srcRect = curSrc, .customPalette = renderSettings.customPalette});
// right top corner
curDest = {
@ -210,13 +253,13 @@ RDPQGraphics::~RDPQGraphics()
void RDPQGraphics::init()
{
rdpq_init();
rdpq_debug_start();
// rdpq_debug_start();
initialized_ = true;
}
void RDPQGraphics::destroy()
{
rdpq_debug_stop();
// rdpq_debug_stop();
rdpq_close();
initialized_ = false;
}

View File

@ -52,6 +52,9 @@ static void goToDistributionPokemonListMenu(void* context, DistributionPokemonLi
{
auto sceneContext = new DistributionPokemonListSceneContext;
sceneContext->listType = type;
sceneContext->bButtonMeansUserWantsToSwitchCartridge = false;
sceneContext->numMenuEntries = 0;
sceneContext->menuEntries = nullptr;
MenuScene* scene = static_cast<MenuScene*>(context);
SceneManager& sceneManager = scene->getDependencies().sceneManager;

View File

@ -8,6 +8,8 @@
#include "gen2/Gen2GameReader.h"
#include "menu/MenuEntries.h"
static const Rectangle tpakDetectWidgetBounds = {60, 44, 200, 116};
static void dialogFinishedCallback(void* context)
{
InitTransferPakScene* scene = (InitTransferPakScene*)context;
@ -28,6 +30,7 @@ InitTransferPakScene::InitTransferPakScene(SceneDependencies& deps, void*)
.current = &tpakDetectWidget_
})
, diagData_({0})
, pokeMe64TextSettings_()
, playerName_()
, gameTypeString_(nullptr)
{
@ -47,6 +50,12 @@ void InitTransferPakScene::init()
setupTPakDetectWidget();
setFocusChain(&tpakDetectWidgetSegment_);
pokeMe64TextSettings_ = TextRenderSettings{
.fontId = arialId_,
.fontStyleId = fontStyleWhiteId_,
.halign = ALIGN_CENTER
};
}
void InitTransferPakScene::destroy()
@ -59,7 +68,7 @@ void InitTransferPakScene::destroy()
void InitTransferPakScene::render(RDPQGraphics& gfx, const Rectangle& sceneBounds)
{
//gfx.fillRectangle(Rectangle{.x = 0, .y = 0, .width = 100, .height = 100}, RGBA16(31, 0, 0, 1));
gfx.drawText(Rectangle{0, 10, 320, 16}, "PokeMe64 by risingPhil. Version 0.1", pokeMe64TextSettings_);
tpakDetectWidget_.render(gfx, sceneBounds);
SceneWithDialogWidget::render(gfx, sceneBounds);
@ -134,6 +143,22 @@ void InitTransferPakScene::onTransferPakWidgetStateChanged(TransferPakWidgetStat
dialogWidget_.setVisible(true);
setFocusChain(&dialogFocusChainSegment_);
}
else
{
switch(newState)
{
case TransferPakWidgetState::GB_HEADER_VALIDATION_FAILED:
case TransferPakWidgetState::NO_GAME_FOUND:
case TransferPakWidgetState::NO_TRANSFER_PAK_FOUND:
setDialogDataText(diagData_, "We could not find a suitable game cartridge! Please turn the console off and try again!");
diagData_.userAdvanceBlocked = true;
dialogWidget_.appendDialogData(&diagData_);
dialogWidget_.setVisible(true);
break;
default:
break;
}
}
}
void InitTransferPakScene::setupTPakDetectWidget()
@ -141,13 +166,14 @@ void InitTransferPakScene::setupTPakDetectWidget()
const TransferPakDetectionWidgetStyle style = {
.textSettings = {
.fontId = arialId_,
.fontStyleId = fontStyleWhiteId_
.fontStyleId = fontStyleWhiteId_,
.halign = ALIGN_CENTER
}
};
tpakDetectWidget_.setStyle(style);
tpakDetectWidget_.setStateChangedCallback(tpakWidgetStateChangedCallback, this);
tpakDetectWidget_.setBounds(Rectangle{60, 90, 200, 60});
tpakDetectWidget_.setBounds(tpakDetectWidgetBounds);
}
void InitTransferPakScene::setupDialog(DialogWidgetStyle& style)

View File

@ -142,6 +142,8 @@ void MenuScene::onDialogDone()
dialogWidget_.setVisible(false);
menuList_.setVisible(true);
cursorWidget_.setVisible(true);
// make sure the relevant scroll arrows are made visible again
menuList_.notifyScrollWindowListeners();
setFocusChain(&listFocusChainSegment_);
}
@ -267,6 +269,8 @@ void MenuScene::showDialog(DialogData* diagData)
{
SceneWithDialogWidget::showDialog(diagData);
menuList_.setVisible(false);
scrollArrowUp_.setVisible(false);
scrollArrowDown_.setVisible(false);
cursorWidget_.setVisible(false);
setFocusChain(&dialogFocusChainSegment_);
}

View File

@ -114,6 +114,7 @@ void StatsScene::init()
const char* move4Str;
const char* pokeName;
const char* trainerName;
const char* shinyText;
bool shiny;
menu9SliceSprite_ = sprite_load("rom://menu-bg-9slice.sprite");
@ -147,7 +148,7 @@ void StatsScene::init()
pokeName = gen1GameReader_.getPokemonName(pokeIndex);
trainerName = gen1GameReader_.getTrainerName();
shiny = false;
snprintf(pokeStatsString_, sizeof(pokeStatsString_), "ATK: %u\nDEF: %u\nSPEC: %u\nSPEED: %u", atk, def, specAtk, speed);
snprintf(pokeStatsString_, sizeof(pokeStatsString_), "ATK: %u\nDEF: %u\nSPEC: %u\nSPEED: %u", atk, def, specAtk, speed);
break;
case 2:
gen2_recalculatePokeStats(gen2GameReader_, context_->poke_g2);
@ -178,7 +179,8 @@ void StatsScene::init()
loadPokemonSprite(pokeIndex, shiny);
strncpy(nameBuffer_, pokeName, sizeof(nameBuffer_) - 1);
shinyText = (shiny) ? "Shiny " : "";
snprintf(nameBuffer_, sizeof(nameBuffer_) - 1, "%s%s", shinyText, pokeName);
snprintf(levelAndNumberBuffer_, sizeof(levelAndNumberBuffer_), "L %hu No. %hu\nHP: %u/%u", level, pokeNumber, hp, hp);
snprintf(otInfoString_, sizeof(otInfoString_), "TID: %u\nOT: %s", trainerID, context_->trainerName);
snprintf(movesString, sizeof(movesString), "%s\n%s\n%s\n%s", move1Str, move2Str, move3Str, move4Str);
@ -187,11 +189,11 @@ void StatsScene::init()
{
if(context_->isEgg)
{
setDialogDataText(diag_, "%s received a %s EGG!Take good care of it!", trainerName, pokeName);
setDialogDataText(diag_, "%s received a %s%s EGG!Take good care of it!", trainerName, shinyText, pokeName);
}
else
{
setDialogDataText(diag_, "%s received %s!\nTake good care of it!", trainerName, pokeName);
setDialogDataText(diag_, "%s received a %s%s!\nTake good care of it!", trainerName, shinyText, pokeName);
}
showDialog(&diag_);
}

View File

@ -3,6 +3,30 @@
#include "transferpak/TransferPakRomReader.h"
#include "transferpak/TransferPakSaveManager.h"
/**
* @brief This function allows you to specify a 32 bit RGBA color by specifying separate color components
* and converting it to a RGBA16 uint16_t value at compile time
*
* @param r 8 bit red value
* @param g 8 bit green value
* @param b 8 bit blue value
* @param a 8 bit alpha value
*/
constexpr uint16_t colorToRGBA16(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
return (((uint16_t)r >> 3) << 11) | (((uint16_t)g >> 3) << 6) | (((uint16_t)b >> 3) << 1) | (a >> 7);
}
static const Rectangle textBounds = {0, 100, 200, 20};
static const Rectangle cartridgeLabelBounds = {9, 26, 70, 62};
static const uint16_t paletteBlue[] = {0, colorToRGBA16(0x20, 0x30, 0x81, 0xFF), colorToRGBA16(0x15, 0x3B, 0xB0, 0xFF), 0, 0, 0, 0, 0};
static const uint16_t paletteRed[] = {0, colorToRGBA16(0xA7, 0x1F, 0x1B, 0xFF), colorToRGBA16(0xAA, 0x2A, 0x2A, 0xFF), 0, 0, 0, 0, 0};
static const uint16_t paletteYellow[] = {0, colorToRGBA16(0xEA, 0xA8, 0x2C, 0xFF), colorToRGBA16(0xF4, 0xB0, 0x2E, 0xFF), 0, 0, 0, 0, 0};
static const uint16_t paletteGold[] = {0, colorToRGBA16(0x8A, 0x86, 0x48, 0xFF), colorToRGBA16(0x88, 0x89, 0x4B, 0xFF), 0, 0, 0, 0, 0};
static const uint16_t paletteSilver[] = {0, colorToRGBA16(0x90, 0x8D, 0x85, 0xFF), colorToRGBA16(0xA2, 0x9E, 0x98, 0xFF), 0, 0, 0, 0, 0};
static const uint16_t paletteCrystal[] = {0, colorToRGBA16(0x55, 0x7A, 0x77, 0xFF), colorToRGBA16(0x72, 0x9E, 0xA4, 0xFF), 0, 0, 0, 0, 0};
#if 0
#include "gen2/Gen2GameReader.h"
static void doRandomShit(TransferPakManager& tpakManager)
@ -22,20 +46,33 @@ TransferPakDetectionWidget::TransferPakDetectionWidget(AnimationManager& animMan
, animManager_(animManager)
, tpakManager_(pakManager)
, bounds_({0})
, textBounds_({.x = 0, .y = 0, .width = 200, .height = 20})
, currentState_(TransferPakWidgetState::UNKNOWN)
, previousInputState_({0})
, gen1Type_(Gen1GameType::INVALID)
, gen2Type_(Gen2GameType::INVALID)
, stateChangedCallback_(nullptr)
, stateChangedCallbackContext_(nullptr)
, cartridgeIconSprite_(nullptr)
, cartridgeLabelSprite_(nullptr)
, cartridgeIconRenderSettings_()
, cartridgeLabelRenderSettings_()
, focused_(false)
, visible_(true)
{
cartridgeIconSprite_ = sprite_load("rom://cartridge-icon.sprite");
cartridgeLabelSprite_ = sprite_load("rom://cartridge-label-unknown.sprite");
}
TransferPakDetectionWidget::~TransferPakDetectionWidget()
{
sprite_free(cartridgeIconSprite_);
cartridgeIconSprite_ = nullptr;
if(cartridgeLabelSprite_)
{
sprite_free(cartridgeLabelSprite_);
cartridgeLabelSprite_ = nullptr;
}
}
bool TransferPakDetectionWidget::isFocused() const
@ -95,6 +132,16 @@ bool TransferPakDetectionWidget::handleUserInput(const joypad_inputs_t& userInpu
void TransferPakDetectionWidget::render(RDPQGraphics& gfx, const Rectangle& parentBounds)
{
//gfx.fillRectangle(bounds_, RGBA32(0xFF, 0, 0, 0xFF));
const Rectangle absoluteIconBounds = {bounds_.x + ((bounds_.width - 88) / 2), bounds_.y, 88, 100};
gfx.drawSprite(absoluteIconBounds, cartridgeIconSprite_, cartridgeIconRenderSettings_);
if(cartridgeLabelSprite_)
{
const Rectangle absoluteLabelBounds = addOffset(cartridgeLabelBounds, absoluteIconBounds);
gfx.drawSprite(absoluteLabelBounds, cartridgeLabelSprite_, cartridgeLabelRenderSettings_);
}
switch(currentState_)
{
case TransferPakWidgetState::UNKNOWN:
@ -155,6 +202,7 @@ void TransferPakDetectionWidget::switchState(TransferPakWidgetState previousStat
switchState(state, newState);
return;
case TransferPakWidgetState::GAME_FOUND:
updateCartridgeIcon();
// doRandomShit(tpakManager_);
break;
default:
@ -170,13 +218,13 @@ void TransferPakDetectionWidget::switchState(TransferPakWidgetState previousStat
void TransferPakDetectionWidget::renderUnknownState(RDPQGraphics& gfx, const Rectangle& parentBounds)
{
const Rectangle absoluteTextBounds = addOffset(textBounds_, bounds_);
gfx.drawText(absoluteTextBounds, "Press A to check the transfer pak...", style_.textSettings);
const Rectangle absoluteTextBounds = addOffset(textBounds, bounds_);
gfx.drawText(absoluteTextBounds, "Press A to start", style_.textSettings);
}
void TransferPakDetectionWidget::renderErrorState(RDPQGraphics& gfx, const Rectangle& parentBounds)
{
const Rectangle absoluteTextBounds = addOffset(textBounds_, bounds_);
const Rectangle absoluteTextBounds = addOffset(textBounds, bounds_);
const char* errorText;
switch(currentState_)
@ -238,4 +286,64 @@ bool TransferPakDetectionWidget::detectGameType()
gen2Type_ = gen2_determineGameType(cartridgeHeader);
return (gen1Type_ != Gen1GameType::INVALID || gen2Type_ != Gen2GameType::INVALID);
}
void TransferPakDetectionWidget::updateCartridgeIcon()
{
const char* labelSpritePath = nullptr;
if(gen1Type_ != Gen1GameType::INVALID)
{
switch(gen1Type_)
{
case Gen1GameType::BLUE:
cartridgeIconRenderSettings_.customPalette.numColors = sizeof(paletteBlue) / sizeof(paletteBlue[0]);
cartridgeIconRenderSettings_.customPalette.colorsRGBA16 = paletteBlue;
labelSpritePath = "rom://cartridge-label-blue.sprite";
break;
case Gen1GameType::RED:
cartridgeIconRenderSettings_.customPalette.numColors = sizeof(paletteRed) / sizeof(paletteRed[0]);
cartridgeIconRenderSettings_.customPalette.colorsRGBA16 = paletteRed;
labelSpritePath = "rom://cartridge-label-red.sprite";
break;
case Gen1GameType::YELLOW:
cartridgeIconRenderSettings_.customPalette.numColors = sizeof(paletteYellow) / sizeof(paletteYellow[0]);
cartridgeIconRenderSettings_.customPalette.colorsRGBA16 = paletteYellow;
labelSpritePath = "rom://cartridge-label-yellow.sprite";
break;
default:
break;
}
}
else if(gen2Type_ != Gen2GameType::INVALID)
{
switch(gen2Type_)
{
case Gen2GameType::GOLD:
cartridgeIconRenderSettings_.customPalette.numColors = sizeof(paletteGold) / sizeof(paletteGold[0]);
cartridgeIconRenderSettings_.customPalette.colorsRGBA16 = paletteGold;
labelSpritePath = "rom://cartridge-label-gold.sprite";
break;
case Gen2GameType::SILVER:
cartridgeIconRenderSettings_.customPalette.numColors = sizeof(paletteSilver) / sizeof(paletteSilver[0]);
cartridgeIconRenderSettings_.customPalette.colorsRGBA16 = paletteSilver;
labelSpritePath = "rom://cartridge-label-silver.sprite";
break;
case Gen2GameType::CRYSTAL:
cartridgeIconRenderSettings_.customPalette.numColors = sizeof(paletteCrystal) / sizeof(paletteCrystal[0]);
cartridgeIconRenderSettings_.customPalette.colorsRGBA16 = paletteCrystal;
labelSpritePath = "rom://cartridge-label-crystal.sprite";
break;
default:
break;
}
}
if(labelSpritePath)
{
if(cartridgeLabelSprite_)
{
sprite_free(cartridgeLabelSprite_);
}
cartridgeLabelSprite_ = sprite_load(labelSpritePath);
}
}