Add StatsScene for showing obtained pokemon

This commit is contained in:
Philippe Symons 2024-08-13 19:18:14 +02:00
parent 4a8ecb6017
commit f516c6d138
17 changed files with 537 additions and 34 deletions

View File

@ -37,6 +37,7 @@ filesystem/%.sprite: assets/%.png
$(N64_MKSPRITE) $(MKSPRITE_FLAGS) -o filesystem "$<"
filesystem/Arial.font64: MKFONT_FLAGS+=--size 11 --outline 1.0 --char-spacing 1.0 --range 20-E9
filesystem/Arial-small.font64: MKFONT_FLAGS+=--size 10 --outline 1.0 --char-spacing 1.0 --range 20-E9
filesystem/menu-bg-9slice.sprite: MKSPRITE_FLAGS += -f RGBA16

View File

@ -15,6 +15,7 @@ typedef struct TextRenderSettings
} TextRenderSettings;
typedef struct SpriteRenderSettings SpriteRenderSettings;
typedef struct SurfaceRenderSettings SurfaceRenderSettings;
/**
* @brief This class abstracts operations done with the RDPQ graphics API in libdragon
@ -64,6 +65,11 @@ public:
*/
void drawSprite(const Rectangle& dstRect, sprite_t* sprite, const SpriteRenderSettings& renderSettings);
/**
* Same as drawSprite() but then for surface_t objects
*/
void drawSurface(const Rectangle& dstRect, const surface_t* surface, const SurfaceRenderSettings& renderSettings);
const Rectangle& getClippingRectangle() const;
void setClippingRectangle(const Rectangle& clipRect);
void resetClippingRectangle();

View File

@ -60,4 +60,17 @@ typedef struct SpriteRenderSettings
float rotationAngle;
} SpriteRenderSettings;
typedef struct SurfaceRenderSettings
{
/**
* Source region within the sprite that needs to be rendered (partial rendering)
*/
Rectangle srcRect;
/**
* @brief Rotation angle in radians
*/
float rotationAngle;
} SurfaceRenderSettings;
#endif

View File

@ -12,4 +12,7 @@ extern const uint32_t gen2MenuEntriesSize;
extern MenuItemData gen2CrystalMenuEntries[];
extern const uint32_t gen2CrystalMenuEntriesSize;
extern MenuItemData gen2DecorationMenuEntries[];
extern const uint32_t gen2DecorationMenuEntriesSize;
#endif

View File

@ -21,6 +21,7 @@ void goToTestScene(void* context, const void* param);
void goToGen1DistributionPokemonMenu(void* context, const void* param);
void goToGen2DistributionPokemonMenu(void* context, const void* param);
void goToGen2PCNYDistributionPokemonMenu(void* context, const void* param);
void goToGen2DecorationMenu(void* context, const void* param);
void gen1PrepareToTeachPikachu(void* context, const void* param);
void gen1TeachPikachu(void* context, const void* param);

View File

@ -17,6 +17,7 @@ enum class SceneType
INIT_TRANSFERPAK,
MENU,
DISTRIBUTION_POKEMON_LIST,
STATS,
TEST
};

View File

@ -62,6 +62,7 @@ private:
std::vector<SceneHistorySegment> sceneHistory_;
SceneDependencies sceneDeps_;
uint64_t blockInputStartTime_;
IScene* scene_;
SceneType newSceneType_;
void* newSceneContext_;

View File

@ -0,0 +1,68 @@
#ifndef _STATSSCENE_H
#define _STATSSCENE_H
#include "scenes/SceneWithDialogWidget.h"
#include "gen1/Gen1Common.h"
#include "gen2/Gen2Common.h"
#include "core/Sprite.h"
#include "transferpak/TransferPakRomReader.h"
#include "transferpak/TransferPakSaveManager.h"
#include "gen1/Gen1GameReader.h"
#include "gen2/Gen2GameReader.h"
typedef struct StatsSceneContext
{
union {
Gen1TrainerPokemon poke_g1;
Gen2TrainerPokemon poke_g2;
};
bool showReceivedPokemonDialog;
bool isEgg;
} StatsSceneContext;
/**
* Shows the stats for a pokemon
*/
class StatsScene : public SceneWithDialogWidget
{
public:
StatsScene(SceneDependencies& deps, void* context);
virtual ~StatsScene();
void init() override;
void destroy() override;
void render(RDPQGraphics& gfx, const Rectangle& sceneBounds) override;
void onDialogFinished();
protected:
void setupDialog(DialogWidgetStyle& style) override;
private:
void loadPokemonSprite(uint8_t pokeIndex, bool shiny);
DialogData diag_;
TransferPakRomReader romReader_;
TransferPakSaveManager saveManager_;
Gen1GameReader gen1GameReader_;
Gen2GameReader gen2GameReader_;
sprite_t* menu9SliceSprite_;
SpriteRenderSettings backgroundRenderSettings_;
uint8_t fontArialSmallId_;
uint8_t fontArialSmallWhiteId_;
TextRenderSettings textSettings_;
TextRenderSettings smallTextSettings_;
TextRenderSettings statsSettings_;
Rectangle spriteBounds_;
char nameBuffer_[15];
char levelAndNumberBuffer_[40];
char pokeStatsString_[150];
char otInfoString_[40];
char movesString[256];
StatsSceneContext* context_;
surface_t pokeSpriteSurface_;
};
void deleteStatsSceneContext(void* context);
#endif

@ -1 +1 @@
Subproject commit 7b96cae992f827921032a4c60e773efd213039f5
Subproject commit 47ec404aac43c46bf1adf65e49c7da10946ad587

View File

@ -16,6 +16,7 @@ uint8_t FontManager::getFont(const char* fontUri)
.fontId = nextFontId_
};
fontMap_.emplace(fontUri, entry);
++nextFontId_;
rdpq_text_register_font(entry.fontId, entry.font);
return entry.fontId;

View File

@ -167,6 +167,35 @@ static void render_sprite_ninegrid(const Rectangle &dstRect, sprite_t *sprite, c
render_sprite_normal(curDest, sprite, {.renderMode = SpriteRenderMode::NORMAL, .srcRect = curSrc});
}
// same as render_sprite_normal, but for surface_t
static void render_surface(const Rectangle &dstRect, const surface_t *surface, const SurfaceRenderSettings &renderSettings)
{
if (!isZeroSizeRectangle(renderSettings.srcRect))
{
const rdpq_blitparms_t blitParams = {
.s0 = renderSettings.srcRect.x,
.t0 = renderSettings.srcRect.y,
.width = renderSettings.srcRect.width,
.height = renderSettings.srcRect.height,
.scale_x = static_cast<float>(dstRect.width) / renderSettings.srcRect.width,
.scale_y = static_cast<float>(dstRect.height) / renderSettings.srcRect.height,
.theta = renderSettings.rotationAngle
};
rdpq_tex_blit(surface, dstRect.x, dstRect.y, &blitParams);
}
else
{
const rdpq_blitparms_t blitParams = {
.scale_x = static_cast<float>(dstRect.width) / surface->width,
.scale_y = static_cast<float>(dstRect.height) / surface->height,
.theta = renderSettings.rotationAngle
};
rdpq_tex_blit(surface, dstRect.x, dstRect.y, &blitParams);
}
}
RDPQGraphics::RDPQGraphics()
: clipRect_({0})
, initialized_(false)
@ -282,6 +311,27 @@ void RDPQGraphics::drawSprite(const Rectangle &dstRect, sprite_t *sprite, const
}
}
void RDPQGraphics::drawSurface(const Rectangle& dstRect, const surface_t* surface, const SurfaceRenderSettings& renderSettings)
{
rdpq_mode_begin();
rdpq_set_mode_standard();
rdpq_mode_alphacompare(1); // colorkey (draw pixel with alpha >= 1)
switch(surface_get_format(surface))
{
case FMT_RGBA32:
case FMT_IA8:
case FMT_IA16:
rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY);
break;
default:
break;
}
rdpq_mode_filter(FILTER_BILINEAR);
rdpq_mode_end();
render_surface(dstRect, surface, renderSettings);
}
const Rectangle& RDPQGraphics::getClippingRectangle() const
{
return clipRect_;

View File

@ -27,22 +27,11 @@ MenuItemData gen2MenuEntries[] = {
},
{
.title = "PCNY Pokémon",
.onConfirmAction = goToGen2PCNYDistributionPokemonMenu,
.onConfirmAction = goToGen2PCNYDistributionPokemonMenu
},
{
.title = "Unlock Pikachu Bed",
.onConfirmAction = gen2SetEventFlag,
.itemParam = &GEN2_EVENTFLAG_DECORATION_PIKACHU_BED
},
{
.title = "Unlock Unown Doll",
.onConfirmAction = gen2SetEventFlag,
.itemParam = &GEN2_EVENTFLAG_DECORATION_UNOWN_DOLL
},
{
.title = "Unlock Tentacool Doll",
.onConfirmAction = gen2SetEventFlag,
.itemParam = &GEN2_EVENTFLAG_DECORATION_TENTACOOL_DOLL
.title = "Unlock Decoration",
.onConfirmAction = goToGen2DecorationMenu
}
};
@ -62,20 +51,29 @@ MenuItemData gen2CrystalMenuEntries[] = {
.onConfirmAction = gen2ReceiveGSBall
},
{
.title = "Unlock Pikachu Bed",
.title = "Unlock Decoration",
.onConfirmAction = goToGen2DecorationMenu
}
};
const uint32_t gen2CrystalMenuEntriesSize = sizeof(gen2CrystalMenuEntries);
MenuItemData gen2DecorationMenuEntries[] = {
{
.title = "Pikachu Bed",
.onConfirmAction = gen2SetEventFlag,
.itemParam = &GEN2_EVENTFLAG_DECORATION_PIKACHU_BED
},
{
.title = "Unlock Unown Doll",
.title = "Unown Doll",
.onConfirmAction = gen2SetEventFlag,
.itemParam = &GEN2_EVENTFLAG_DECORATION_UNOWN_DOLL
},
{
.title = "Unlock Tentacool Doll",
.title = "Tentacool Doll",
.onConfirmAction = gen2SetEventFlag,
.itemParam = &GEN2_EVENTFLAG_DECORATION_TENTACOOL_DOLL
}
};
const uint32_t gen2CrystalMenuEntriesSize = sizeof(gen2CrystalMenuEntries);
const uint32_t gen2DecorationMenuEntriesSize = sizeof(gen2DecorationMenuEntries);

View File

@ -2,6 +2,8 @@
#include "menu/MenuEntries.h"
#include "core/RDPQGraphics.h"
#include "scenes/DistributionPokemonListScene.h"
#include "scenes/StatsScene.h"
#include "scenes/MenuScene.h"
#include "scenes/SceneManager.h"
#include "gen2/Gen2GameReader.h"
#include "transferpak/TransferPakManager.h"
@ -114,7 +116,6 @@ void goToTestScene(void* context, const void* param)
void goToGen1DistributionPokemonMenu(void* context, const void*)
{
goToDistributionPokemonListMenu(context, DistributionPokemonListType::GEN1);
}
void goToGen2DistributionPokemonMenu(void* context, const void*)
@ -127,6 +128,17 @@ void goToGen2PCNYDistributionPokemonMenu(void* context, const void* param)
goToDistributionPokemonListMenu(context, DistributionPokemonListType::GEN2_POKEMON_CENTER_NEW_YORK);
}
void goToGen2DecorationMenu(void* context, const void* param)
{
MenuScene* scene = static_cast<MenuScene*>(context);
auto newSceneContext = new MenuSceneContext{
.menuEntries = gen2DecorationMenuEntries,
.numMenuEntries = gen2DecorationMenuEntriesSize / sizeof(gen2DecorationMenuEntries[0])
};
scene->getDependencies().sceneManager.switchScene(SceneType::MENU, deleteMenuSceneContext, newSceneContext);
}
void gen1PrepareToTeachPikachu(void* context, const void* param)
{
MenuScene* scene = static_cast<MenuScene*>(context);
@ -382,4 +394,4 @@ void gen2SetEventFlag(void* context, const void* param)
tpakManager.setRAMEnabled(false);
scene->showDialog(messageData);
}
}

View File

@ -1,5 +1,6 @@
#include "scenes/DistributionPokemonListScene.h"
#include "scenes/SceneManager.h"
#include "scenes/StatsScene.h"
#include "transferpak/TransferPakManager.h"
static DistributionPokemonListSceneContext* convert(void* context)
@ -68,27 +69,38 @@ void DistributionPokemonListScene::triggerPokemonInjection(const void* data)
void DistributionPokemonListScene::injectPokemon(const void* data)
{
StatsSceneContext* statsContext;
const Gen1DistributionPokemon* g1Poke;
const Gen2DistributionPokemon* g2Poke;
const char* trainerName;
const char* pokeName;
deps_.tpakManager.setRAMEnabled(true);
statsContext = new StatsSceneContext{
.showReceivedPokemonDialog = true
};
switch(convert(context_)->listType)
{
case DistributionPokemonListType::GEN1:
g1Poke = static_cast<const Gen1DistributionPokemon*>(data);
trainerName = gen1Reader_.getTrainerName();
pokeName = gen1Reader_.getPokemonName(g1Poke->poke.poke_index);
gen1Reader_.addDistributionPokemon((*g1Poke));
statsContext->poke_g1 = g1Poke->poke;
// I have not used gen1Reader_->addDistributionPokemon here because I want to show the resulting pokemon in a stats screen
// gen1_prepareDistributionPokemon() + addPokemon() gives me access to the resulting Gen1TrainerPokemon instance
// in which things are done like IV generation, OT name decision, OT id
gen1_prepareDistributionPokemon(gen1Reader_, (*g1Poke), statsContext->poke_g1, trainerName);
gen1Reader_.addPokemon(statsContext->poke_g1, trainerName);
break;
case DistributionPokemonListType::GEN2:
case DistributionPokemonListType::GEN2_POKEMON_CENTER_NEW_YORK:
g2Poke = static_cast<const Gen2DistributionPokemon*>(data);
trainerName = gen2Reader_.getTrainerName();
pokeName = gen2Reader_.getPokemonName(g2Poke->poke.poke_index);
gen2Reader_.addDistributionPokemon((*g2Poke));
statsContext->poke_g2 = g2Poke->poke;
statsContext->isEgg = g2Poke->isEgg;
// I have not used gen2Reader_->addDistributionPokemon here because I want to show the resulting pokemon in a stats screen
// gen2_prepareDistributionPokemon() + addPokemon() gives me access to the resulting Gen2TrainerPokemon instance
// in which things are done like IV generation, OT name decision, OT id, shininess
gen2_prepareDistributionPokemon(gen2Reader_, (*g2Poke), statsContext->poke_g2, trainerName);
gen2Reader_.addPokemon(statsContext->poke_g2, trainerName);
gen2Reader_.finishSave();
break;
default:
@ -104,6 +116,8 @@ void DistributionPokemonListScene::injectPokemon(const void* data)
* to MBC3
*/
deps_.tpakManager.setRAMEnabled(false);
delete statsContext;
statsContext = nullptr;
return;
}
@ -112,12 +126,9 @@ void DistributionPokemonListScene::injectPokemon(const void* data)
// The reason is the same as previous setRAMEnabled(false) statement above
deps_.tpakManager.setRAMEnabled(false);
// operation done. Now the dialog can be advanced and we can show confirmation that the user got the pokémon
dialogWidget_.advanceDialog();
deps_.sceneManager.switchScene(SceneType::STATS, deleteStatsSceneContext, statsContext);
setDialogDataText(diag_, "%s received %s!", trainerName, pokeName);
diag_.userAdvanceBlocked = false;
showDialog(&diag_);
// operation done. Now the dialog can be advanced and we can show confirmation that the user got the pokémon
}
void DistributionPokemonListScene::onDialogDone()

View File

@ -158,6 +158,10 @@ void MenuScene::setupMenu()
},
.margin = {
.top = 5
},
.autogrow = {
.enabled = true,
.maxHeight = 150
}
};
@ -169,7 +173,7 @@ void MenuScene::setupMenu()
};
menuList_.setStyle(listStyle);
menuList_.setBounds(Rectangle{100, 30, 150, 150});
menuList_.setBounds(Rectangle{100, 30, 150, 0});
menuList_.setVisible(true);
cursorWidget_.setStyle(cursorStyle);
cursorWidget_.setVisible(true);

View File

@ -1,10 +1,13 @@
#include "scenes/SceneManager.h"
#include "scenes/TestScene.h"
#include "scenes/StatsScene.h"
#include "scenes/InitTransferPakScene.h"
#include "scenes/DistributionPokemonListScene.h"
#include <libdragon.h>
static const uint16_t INPUT_BLOCK_TIME_AFTER_NEW_SCENE_IN_MS = 500;
SceneManager::SceneManager(RDPQGraphics& gfx, AnimationManager& animationManager, FontManager& fontManager, TransferPakManager& tpakManager)
: sceneHistory_()
, sceneDeps_(SceneDependencies{
@ -44,6 +47,8 @@ void SceneManager::switchScene(SceneType type, void (*deleteContextFunc)(void*),
.context = sceneContext,
.deleteContextFunc = deleteContextFunc
});
blockInputStartTime_ = get_ticks();
}
void SceneManager::goBackToPreviousScene()
@ -80,6 +85,18 @@ void SceneManager::handleUserInput()
{
return;
}
if(blockInputStartTime_)
{
const uint64_t now = get_ticks();
if(TICKS_TO_MS(now - blockInputStartTime_) < INPUT_BLOCK_TIME_AFTER_NEW_SCENE_IN_MS)
{
return;
}
// enough time has passed. reset the blockInputStartTime_
blockInputStartTime_ = 0;
}
scene_->processUserInput();
}
@ -112,6 +129,9 @@ void SceneManager::loadScene()
case SceneType::DISTRIBUTION_POKEMON_LIST:
scene_ = new DistributionPokemonListScene(sceneDeps_, newSceneContext_);
break;
case SceneType::STATS:
scene_ = new StatsScene(sceneDeps_, newSceneContext_);
break;
case SceneType::TEST:
scene_ = new TestScene(sceneDeps_, newSceneContext_);
break;

313
src/scenes/StatsScene.cpp Normal file
View File

@ -0,0 +1,313 @@
#include "scenes/StatsScene.h"
#include "SpriteRenderer.h"
#include "core/FontManager.h"
#include "scenes/SceneManager.h"
#include "transferpak/TransferPakManager.h"
#include "Moves.h"
using OutputFormat = SpriteRenderer::OutputFormat;
static const Rectangle backgroundBounds = {30, 25, 260, 140};
static const Rectangle spriteBounds = {37, 32, 0, 0};
static const Rectangle nameBounds = {90, 37, 150, 16};
static const Rectangle levelAndNumberBounds = {90, 53, 100, 32};
static const Rectangle otInfoBounds = {185, 53, 100, 32};
static const Rectangle statsBounds = {42, 96, 150, 80};
static const Rectangle movesBounds = {185, 96, 150, 80};
static uint8_t getNumBytesPerColorFor(OutputFormat format)
{
switch(format)
{
case OutputFormat::RGB:
return 3;
case OutputFormat::RGBA16:
return 2;
case OutputFormat::RGBA32:
return 4;
default:
return 0;
}
}
static Gen1GameType getGen1TypeFor(uint8_t generation, uint8_t specificGenVersion)
{
if(generation != 1)
{
return Gen1GameType::INVALID;
}
return static_cast<Gen1GameType>(specificGenVersion);
}
static Gen2GameType getGen2TypeFor(uint8_t generation, uint8_t specificGenVersion)
{
if(generation != 2)
{
return Gen2GameType::INVALID;
}
return static_cast<Gen2GameType>(specificGenVersion);
}
static void dialogFinishedCallback(void* context)
{
StatsScene* scene = (StatsScene*)context;
scene->onDialogFinished();
}
StatsScene::StatsScene(SceneDependencies& deps, void* context)
: SceneWithDialogWidget(deps)
, diag_({0})
, romReader_(deps.tpakManager)
, saveManager_(deps.tpakManager)
, gen1GameReader_(romReader_, saveManager_, getGen1TypeFor(deps.generation, deps.specificGenVersion))
, gen2GameReader_(romReader_, saveManager_, getGen2TypeFor(deps.generation, deps.specificGenVersion))
, menu9SliceSprite_(nullptr)
, backgroundRenderSettings_({
.renderMode = SpriteRenderMode::NINESLICE,
.srcRect = Rectangle{6, 6, 6, 6}
})
, fontArialSmallId_(2)
, fontArialSmallWhiteId_(0)
, textSettings_(TextRenderSettings{
.fontId = arialId_,
.fontStyleId = fontStyleWhiteId_
})
, smallTextSettings_(TextRenderSettings{
.fontId = fontArialSmallId_,
.fontStyleId = fontArialSmallWhiteId_
})
, statsSettings_(TextRenderSettings{
.fontId = arialId_,
.fontStyleId = fontStyleWhiteId_,
})
, spriteBounds_(spriteBounds)
, nameBuffer_()
, levelAndNumberBuffer_()
, pokeStatsString_()
, otInfoString_()
, context_(static_cast<StatsSceneContext*>(context))
, pokeSpriteSurface_({0})
{
nameBuffer_[0] = '\0';
nameBuffer_[sizeof(nameBuffer_) - 1] = '\0';
}
StatsScene::~StatsScene()
{
}
void StatsScene::init()
{
uint8_t pokeIndex;
uint8_t level;
uint8_t pokeNumber;
uint16_t hp;
uint16_t trainerID;
uint16_t atk;
uint16_t def;
uint16_t specAtk;
uint16_t specDef;
uint16_t speed;
const char* move1Str;
const char* move2Str;
const char* move3Str;
const char* move4Str;
const char* pokeName;
const char* trainerName;
bool shiny;
menu9SliceSprite_ = sprite_load("rom://menu-bg-9slice.sprite");
fontArialSmallId_ = deps_.fontManager.getFont("rom://Arial-small.font64");
SceneWithDialogWidget::init();
const rdpq_fontstyle_t arialWhite = {
.color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF),
.outline_color = RGBA32(0, 0, 0, 0xFF)
};
deps_.fontManager.registerFontStyle(fontArialSmallId_, fontArialSmallWhiteId_, arialWhite);
deps_.tpakManager.setRAMEnabled(true);
switch(deps_.generation)
{
case 1:
gen1_recalculatePokeStats(gen1GameReader_, context_->poke_g1);
pokeIndex = context_->poke_g1.poke_index;
level = context_->poke_g1.level;
pokeNumber = gen1GameReader_.getPokemonNumber(pokeIndex);
hp = context_->poke_g1.max_hp;
trainerID = context_->poke_g1.original_trainer_ID;
atk = context_->poke_g1.atk;
def = context_->poke_g1.def;
specAtk = context_->poke_g1.special;
speed = context_->poke_g1.speed;
move1Str = getMoveString(static_cast<Move>(context_->poke_g1.index_move1));
move2Str = getMoveString(static_cast<Move>(context_->poke_g1.index_move2));
move3Str = getMoveString(static_cast<Move>(context_->poke_g1.index_move3));
move4Str = getMoveString(static_cast<Move>(context_->poke_g1.index_move4));
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);
break;
case 2:
gen2_recalculatePokeStats(gen2GameReader_, context_->poke_g2);
pokeIndex = context_->poke_g2.poke_index;
level = context_->poke_g2.level;
pokeNumber = pokeIndex;
hp = context_->poke_g2.max_hp;
trainerID = context_->poke_g2.original_trainer_ID;
atk = context_->poke_g2.atk;
def = context_->poke_g2.def;
specAtk = context_->poke_g2.special_atk;
specDef = context_->poke_g2.special_def;
speed = context_->poke_g2.speed;
move1Str = getMoveString(static_cast<Move>(context_->poke_g2.index_move1));
move2Str = getMoveString(static_cast<Move>(context_->poke_g2.index_move2));
move3Str = getMoveString(static_cast<Move>(context_->poke_g2.index_move3));
move4Str = getMoveString(static_cast<Move>(context_->poke_g2.index_move4));
pokeName = gen2GameReader_.getPokemonName(pokeIndex);
trainerName = gen2GameReader_.getTrainerName();
shiny = gen2_isPokemonShiny(context_->poke_g2);
snprintf(pokeStatsString_, sizeof(pokeStatsString_), "ATK: %u\nDEF: %u\nSPEC. ATK: %u\nSPEC. DEF: %u\nSPEED: %u", atk, def, specAtk, specDef, speed);
break;
default:
deps_.tpakManager.setRAMEnabled(false);
return;
}
deps_.tpakManager.setRAMEnabled(false);
loadPokemonSprite(pokeIndex, shiny);
strncpy(nameBuffer_, pokeName, sizeof(nameBuffer_) - 1);
snprintf(levelAndNumberBuffer_, sizeof(levelAndNumberBuffer_), "L %hu No. %hu\nHP: %u/%u", level, pokeNumber, hp, hp);
snprintf(otInfoString_, sizeof(otInfoString_), "TID: %u\nOT: %s", trainerID, trainerName);
snprintf(movesString, sizeof(movesString), "%s\n%s\n%s\n%s", move1Str, move2Str, move3Str, move4Str);
if(context_->showReceivedPokemonDialog)
{
if(context_->isEgg)
{
setDialogDataText(diag_, "%s received a %s EGG!Take good care of it!", trainerName, pokeName);
}
else
{
setDialogDataText(diag_, "%s received %s!\nTake good care of it!", trainerName, pokeName);
}
showDialog(&diag_);
}
}
void StatsScene::destroy()
{
SceneWithDialogWidget::destroy();
sprite_free(menu9SliceSprite_);
menu9SliceSprite_ = nullptr;
if(pokeSpriteSurface_.buffer)
{
surface_free(&pokeSpriteSurface_);
}
}
void StatsScene::render(RDPQGraphics& gfx, const Rectangle& sceneBounds)
{
SceneWithDialogWidget::render(gfx, sceneBounds);
gfx.drawSprite(backgroundBounds, menu9SliceSprite_, backgroundRenderSettings_);
gfx.drawSurface(spriteBounds_, &pokeSpriteSurface_, {0});
gfx.drawText(nameBounds, nameBuffer_, textSettings_);
gfx.drawText(levelAndNumberBounds, levelAndNumberBuffer_, smallTextSettings_);
gfx.drawText(otInfoBounds, otInfoString_, smallTextSettings_);
gfx.drawText(statsBounds, pokeStatsString_, smallTextSettings_);
gfx.drawText(movesBounds, movesString, smallTextSettings_);
}
void StatsScene::onDialogFinished()
{
deps_.sceneManager.goBackToPreviousScene();
}
void StatsScene::setupDialog(DialogWidgetStyle& style)
{
style.background.sprite = menu9SliceSprite_;
style.background.spriteSettings = {
.renderMode = SpriteRenderMode::NINESLICE,
.srcRect = { 6, 6, 6, 6 }
};
SceneWithDialogWidget::setupDialog(style);
dialogWidget_.setOnDialogFinishedCallback(dialogFinishedCallback, this);
dialogWidget_.setVisible(false);
}
void StatsScene::loadPokemonSprite(uint8_t pokeIndex, bool shiny)
{
SpriteRenderer renderer;
uint8_t* spriteBuffer;
uint8_t* outputBuffer;
uint16_t colorPalette[4];
uint8_t spriteWidthInTiles;
uint8_t spriteHeightInTiles;
uint8_t spriteWidthInPixels;
uint8_t spriteHeightInPixels;
const OutputFormat outputFormat = OutputFormat::RGBA16;
const uint8_t numBytesPerColor = getNumBytesPerColorFor(outputFormat);
switch(deps_.generation)
{
case 1:
{
Gen1PokeStats pokeStats;
gen1GameReader_.readPokemonStatsForIndex(pokeIndex, pokeStats);
gen1GameReader_.readColorPalette(gen1GameReader_.getColorPaletteIndexByPokemonNumber(pokeStats.pokedex_number), colorPalette);
spriteBuffer = gen1GameReader_.decodeSprite(pokeStats.sprite_bank, pokeStats.pointer_to_frontsprite);
spriteWidthInTiles = MAX_SPRITE_TILES_WIDTH;
spriteHeightInTiles = MAX_SPRITE_TILES_HEIGHT;
break;
}
case 2:
{
Gen2PokeStats pokeStats;
uint8_t bankIndex;
uint16_t bankPointer;
gen2GameReader_.readPokemonStatsForIndex(pokeIndex, pokeStats);
gen2GameReader_.readFrontSpritePointer(pokeIndex, bankIndex, bankPointer);
gen2GameReader_.readSpriteDimensions(pokeStats, spriteWidthInTiles, spriteHeightInTiles);
gen2GameReader_.readColorPaletteForPokemon(pokeIndex, shiny, colorPalette);
spriteBuffer = gen2GameReader_.decodeSprite(bankIndex, bankPointer);
break;
}
default:
return;
}
outputBuffer = renderer.draw(spriteBuffer, outputFormat, colorPalette, spriteWidthInTiles, spriteHeightInTiles, true);
spriteWidthInPixels = spriteWidthInTiles * 8;
spriteHeightInPixels = spriteHeightInTiles * 8;
// due to alignment constraints, we should use the surface_alloc function to ensure that it is done correctly
// and respect its resulting stride field
pokeSpriteSurface_ = surface_alloc(FMT_RGBA16, spriteWidthInPixels, spriteHeightInPixels);
spriteBounds_.width = spriteWidthInPixels;
spriteBounds_.height = spriteHeightInPixels;
// now copy the sprite to the surface
const uint32_t actualStride = spriteWidthInPixels * numBytesPerColor;
for(uint8_t i=0; i < spriteHeightInPixels; ++i)
{
uint8_t* src = outputBuffer + (i * actualStride);
uint8_t* dst = ((uint8_t*)(pokeSpriteSurface_.buffer)) + (i * pokeSpriteSurface_.stride);
// copy the source buffer to the surface line by line, but advancing with the surface stride on every new row
memcpy(dst, src, actualStride);
}
}
void deleteStatsSceneContext(void* context)
{
auto sceneContext = static_cast<StatsSceneContext*>(context);
delete sceneContext;
}