Work on adding a PokeShop (incomplete, WIP, untested)

This commit is the start of the work to add a Pokéshop, which would let you buy starters or version exclusives
for in-game money.

But it's far from complete. This commit lays some of the groundwork, but can't even get compiled yet.
This commit is contained in:
Philippe Symons
2026-01-15 22:13:46 +01:00
parent d106863b50
commit 8e69ad9437
4 changed files with 335 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
#ifndef _POKESHOPSCENE_H
#define _POKESHOPSCENE_H
#include "scenes/MenuScene.h"
#include "transferpak/TransferPakRomReader.h"
#include "transferpak/TransferPakSaveManager.h"
#include "gen1/Gen1GameReader.h"
#include "gen2/Gen2GameReader.h"
#include "widget/PokeShopMenuItemWidget.h"
/**
* @brief This scene implementation gives you a list of pokémon you
* can buy from the PokéShop and inject into your cartridge save by selecting one
*/
class PokeShopScene : public MenuScene
{
public:
PokeShopScene(SceneDependencies& deps, void* context);
virtual ~PokeShopScene();
void init() override;
void destroy() override;
bool handleUserInput(joypad_port_t port, const joypad_inputs_t& inputs) override;
/**
* This function will start the pokémon injection. and show a non-skippable "saving" dialog
* The actual injection will be done on the next handleUserInput() call.
* That will ensure that at least 1 render() call has been handled before we start doing the work
*/
void triggerPokemonInjection(const void* data);
/**
* @brief The core functionality of this class: it will inject the selected pokémon into your cartridge save.
*
* @param data a pointer to the Gen1DistributionPokemon or Gen2DistributionPokemon instance you want to inject.
*/
void injectPokemon(const void* data);
void onDialogDone() override;
protected:
void setupMenu() override;
private:
void loadShopList();
TransferPakRomReader romReader_;
TransferPakSaveManager saveManager_;
Gen1GameReader gen1Reader_;
Gen2GameReader gen2Reader_;
PokemonPartyIconFactory iconFactory_;
ListItemFiller<VerticalList, PokeShopMenuItemData, PokeShopMenuItem, PokeShopMenuItemStyle> customListFiller_;
DialogData diag_;
sprite_t* iconBackgroundSprite_;
const void* pokeToInject_;
};
#endif

View File

@@ -0,0 +1,110 @@
#ifndef _POKESHOPMENUITEMWIDGET_H
#define _POKESHOPMENUITEMWIDGET_H
#include "widget/PokemonPartyIconWidget.h"
#include "widget/MenuItemWidget.h"
typedef struct PokeShopMenuItemStyle
{
/**
* width and height for the MenuItemWidget
*/
Dimensions size;
struct {
/**
* (optional) background sprite
*/
sprite_t* sprite;
/*
* RenderSettings that influence how the backgroundSprite is
* being rendered
*/
SpriteRenderSettings spriteSettings;
} background;
struct {
PokemonPartyIconWidgetStyle style;
Rectangle bounds;
} icon;
struct {
/**
* These are the text settings for when the MenuItemWidget is NOT focused by the user
*/
TextRenderSettings labelNotFocused;
/**
* These are the text render settings for when the MenuItemWidget is focused by the user
*/
TextRenderSettings labelFocused;
/**
* @brief the bounds for the price label
*/
Rectangle bounds;
} title;
struct {
/**
* These are the text settings for when the MenuItemWidget is NOT focused by the user
*/
TextRenderSettings labelNotFocused;
/**
* These are the text render settings for when the MenuItemWidget is focused by the user
*/
TextRenderSettings labelFocused;
/**
* @brief the bounds for the price label
*/
Rectangle bounds;
} price;
} PokeShopMenuItemStyle;
typedef struct PokeShopMenuItemData : public MenuItemData
{
PokemonPartyIconWidgetData iconData;
uint32_t price;
} PokeShopMenuItemData;
/**
* @brief This is a custom MenuItem widget to display distribution event pokemon in a vertical list menu.
*/
class PokeShopMenuItem : public IWidget
{
public:
PokeShopMenuItem();
virtual ~PokeShopMenuItem();
const PokeShopMenuItemData& getData() const;
void setData(const PokeShopMenuItemData& data);
void setStyle(const PokeShopMenuItemStyle& style);
bool isFocused() const override;
void setFocused(bool isFocused) override;
bool isVisible() const override;
void setVisible(bool visible) override;
Rectangle getBounds() const override;
void setBounds(const Rectangle& bounds) override;
Dimensions getSize() const override;
bool handleUserInput(const joypad_inputs_t& userInput) override;
void render(RDPQGraphics& gfx, const Rectangle& parentBounds) override;
protected:
/**
* Executes the onConfirmAction callback (if any)
*/
bool execute();
private:
PokemonPartyIconWidget partyIconWidget_;
PokeShopMenuItemStyle style_;
PokeShopMenuItemData data_;
char priceText_[10];
bool focused_;
bool visible_;
bool aButtonPressed_;
};
#endif

View File

@@ -0,0 +1,39 @@
#include "scenes/PokeShopScene.h"
#include "scenes/SceneManager.h"
#include "scenes/StatsScene.h"
#include "transferpak/TransferPakManager.h"
static const Rectangle menuListBounds = {20, 20, 280, 0};
static const Rectangle imgScrollArrowUpBounds = {.x = 154, .y = 14, .width = 11, .height = 6};
static const Rectangle imgScrollArrowDownBounds = {.x = 154, .y = 220, .width = 11, .height = 6};
static void injectDistributionPokemon(void* context, const void* data)
{
auto scene = static_cast<PokeShopScene*>(context);
scene->triggerPokemonInjection(data);
}
PokeShopScene::PokeShopScene(SceneDependencies& deps, void* context)
: MenuScene(deps, context)
, romReader_(deps.tpakManager)
, saveManager_(deps.tpakManager)
, gen1Reader_(romReader_, saveManager_, static_cast<Gen1GameType>(deps.specificGenVersion), static_cast<Gen1LocalizationLanguage>(deps.localization))
, gen2Reader_(romReader_, saveManager_, static_cast<Gen2GameType>(deps.specificGenVersion), static_cast<Gen2LocalizationLanguage>(deps.localization))
, iconFactory_(romReader_)
, customListFiller_(menuList_)
, diag_()
, iconBackgroundSprite_(nullptr)
, pokeToInject_(nullptr)
{
}
PokeShopScene::~PokeShopScene()
{
}
void PokeShopScene::init()
{
iconBackgroundSprite_ = sprite_load("rom://bg-party-icon.sprite");
loadShopList();
MenuScene::init();
}

View File

@@ -0,0 +1,129 @@
#include "widget/PokeShopMenuItemWidget.h"
PokeShopMenuItem::PokeShopMenuItem()
: partyIconWidget_()
, style_({0})
, data_()
, priceText_({0})
, focused_(false)
, visible_(true)
, aButtonPressed_(false)
{
}
PokeShopMenuItem::~PokeShopMenuItem()
{
}
const PokeShopMenuItemData& PokeShopMenuItem::getData() const
{
return data_;
}
void PokeShopMenuItem::setData(const PokeShopMenuItemData& data)
{
data_ = data;
snprintf(priceText_, sizeof(priceText_), "$%u", data.price);
partyIconWidget_.setData(data.iconData);
}
void PokeShopMenuItem::setStyle(const PokeShopMenuItemStyle& style)
{
style_ = style;
partyIconWidget_.setBounds(style.icon.bounds);
partyIconWidget_.setStyle(style.icon.style);
}
bool PokeShopMenuItem::isFocused() const
{
return focused_;
}
void PokeShopMenuItem::setFocused(bool isFocused)
{
focused_ = isFocused;
partyIconWidget_.setFocused(isFocused);
}
bool PokeShopMenuItem::isVisible() const
{
return visible_;
}
void PokeShopMenuItem::setVisible(bool visible)
{
visible_ = visible;
}
Rectangle PokeShopMenuItem::getBounds() const
{
return Rectangle{.x = 0, .y = 0, .width = style_.size.width, .height = style_.size.height};
}
void PokeShopMenuItem::setBounds(const Rectangle& bounds)
{
// Not relevant: the actual bounds are passed from the VerticalList widget
}
Dimensions PokeShopMenuItem::getSize() const
{
return style_.size;
}
bool PokeShopMenuItem::handleUserInput(const joypad_inputs_t& userInput)
{
// We only care about the A-button press/release for this widget
if(userInput.btn.a)
{
aButtonPressed_ = true;
return true;
}
else if(aButtonPressed_)
{
aButtonPressed_ = false;
return execute();
}
return false;
}
void PokeShopMenuItem::render(RDPQGraphics& gfx, const Rectangle& parentBounds)
{
if(!visible_)
{
return;
}
const Rectangle myBounds = {.x = parentBounds.x, .y = parentBounds.y, .width = style_.size.width, .height = style_.size.height};
if(style_.background.sprite)
{
gfx.drawSprite(myBounds, style_.background.sprite, style_.background.spriteSettings);
}
partyIconWidget_.render(gfx, myBounds);
const Rectangle titleBounds = { myBounds.x + style_.title.bounds.x,
myBounds.y + style_.title.bounds.y,
style_.title.bounds.width,
style_.title.bounds.height };
gfx.drawText(titleBounds, data_.title, (focused_) ? style_.title.labelFocused : style_.title.labelNotFocused);
const Rectangle priceBounds = { myBounds.x + style_.price.bounds.x,
myBounds.y + style_.price.bounds.y,
style_.price.bounds.width,
style_.price.bounds.height };
gfx.drawText(priceBounds, priceText_, (focused_) ? style_.price.labelFocused : style_.price.labelNotFocused);
}
bool PokeShopMenuItem::execute()
{
if(data_.onConfirmAction)
{
data_.onConfirmAction(data_.context, data_.itemParam);
return true;
}
return false;
}