Refactor shop code to use generic Item (#536)

* Turned SpriteObject into a member of JokerObject and CardObject and expiremented with casting

* Extracted shop code to use generic `SpriteObject`

* Extracted shop joker generation to a function

* Renamed object -> item

* Added setters and getters for `SpriteObject` members and updated all accesses to use them with casting (AI used for tedious work and reviewed - model Raptor mini)

* Added sprite_object_set_vx() because sprite_object_set_vy() exists and reordered the function order to have x,y parameters first, then targets

* Added Item object to act as a purchasable interface for the shop - AI used (model Raptor mini)

* Added -fms-extensions and switched to anonymous inheritance

* Fixed anonymous inheritance for joker and added direct access for that too

* fix build error from previous commit + clang-format

* Fixed build errors + clang-format for real this time (I hope...)

* clang-format one last time, I don't understand it anymore

* clang-format is annoying

* Added uses of CHECK_NULL_ARG macros

* Merged item_defs.h into item.h and fixed build error from previous commit

* clang-format...?

* Changed sprite_object_new() into sprite_object_init() and made it accept a parameter instead of returning one to make it less awkward

* Updated sprite_object_init() documentation

* Updated sprite_object_destroy documentation

* More clang-format

* Renamed add_to_inventory to acquire and updated documentation

* Added function documentations

* Update documentation + swap order of consumable and playing card types

* Added documentation for new files (+ small addition to button.h)

* clang-format

* Used typedef

* A bit more documentation

* Changed sho pcode to use generic Item instead of SpriteObject

* clang-format + fixed NULL-check error

* Revert weird clang-format...?

* clang-format for real

* Fixed errors from rebase on log_function_name

* Fixed build errors from rebase on main

* Added some more documentation about first member struct inheritance

* typo fix

* typo fix for real

* Removed SpriteObject field getters/setters (AI used - model Raptor mini)

* Renamed item_acquire -> item_on_acquired

* Added item_can_acquire()

* clang-format

* Fixed some sprite issues but not all...

* Fixed some more issues

* Cleanup JokerObject some more JokerObjects in the shop

* Added `item_destroy()`

* Added item_set_available_to_shop, CHECK_ITEM_TYPE macros + small documentation updates

* Another small JokerObject->Item replacement

* Removed item_set_available_to_shop and replaced with item_roll_new() and item_dispose(), also changed "available_to_shop" semantics to "rollable"

* Re-applied changes from game.c to round.c after rebase on main

* Added partial implementations of item functions for playing cards

* Line endings fix...

* Macro renames, function documentations

* Updated documentations

* Apply fixes from Copilot review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Fix card_object_get_buy_price type checking

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Avoid double pointer cast on dispose

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Apply suggestions from @ricfehr3's code review

Co-authored-by: Rickey <ric@rf3.xyz>

* Remove redundant comments

* Moved error checking before joker_get_random_rarity

Co-authored-by: Rickey <ric@rf3.xyz>

* Fixed build error from rebase

* clang-format

* Added doxygen documentation for Item explaining inheritance

* Removed unnecessary comment

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Rickey <ric@rf3.xyz>
This commit is contained in:
MeirGavish
2026-07-09 09:13:51 +03:00
committed by GitHub
co-authored by Copilot Autofix powered by AI Rickey
parent 369c680cb7
commit d24b0b5619
23 changed files with 796 additions and 329 deletions
+1
View File
@@ -2,6 +2,7 @@
* @file button.h
*
* @brief A button structure containing the common button functionalities
* Including highlight and pressing functionalities.
*/
#ifndef BUTTON_H
+5 -2
View File
@@ -2,7 +2,7 @@
#define CARD_H
#include "deck_types.h"
#include "sprite.h"
#include "item.h"
#include <maxmod.h>
#include <tonc.h>
@@ -54,8 +54,8 @@ typedef struct Card
typedef struct CardObject
{
Item; // First member struct inheritance
Card* card;
SpriteObject* sprite_object;
bool selected;
} CardObject;
@@ -83,4 +83,7 @@ void card_object_set_selected(CardObject* card_object, bool selected);
bool card_object_is_selected(CardObject* card_object);
Sprite* card_object_get_sprite(CardObject* card_object);
int card_object_get_buy_price(Item* card_object);
void card_object_dispose(Item** card_object);
#endif // CARD_H
-1
View File
@@ -4,7 +4,6 @@
#include "sprite.h"
POOL_ENTRY(Sprite, MAX_SPRITES);
POOL_ENTRY(SpriteObject, MAX_SPRITE_OBJECTS);
POOL_ENTRY(Joker, MAX_ACTIVE_JOKERS);
POOL_ENTRY(JokerObject, MAX_ACTIVE_JOKERS);
POOL_ENTRY(Card, MAX_CARDS);
+3 -1
View File
@@ -4,12 +4,13 @@
#include "game/common_ui.h"
#include "game_variables.h"
#include "graphic_utils.h"
#include "item.h"
#include <tonc.h>
#define MAX_DECK_SIZE 52
#define MAX_JOKERS_HELD_SIZE 5 // This doesn't account for negatives right now.
#define MAX_SHOP_JOKERS 2 // TODO: Make this dynamic and allow for other items besides jokers
#define MAX_SHOP_ITEMS 2 // TODO: Make this dynamic
#define MAX_SELECTION_SIZE 5
#define FRAMES(x) (((x) + (g_game_vars.game_speed) - 1) / (g_game_vars.game_speed))
@@ -69,6 +70,7 @@ void game_change_state(enum GameState new_game_state);
enum GameState game_get_state(void);
bool is_joker_owned(int joker_id);
bool joker_object_can_acquire(Item* item);
bool card_is_face(Card* card);
void add_joker(JokerObject* joker_object);
void remove_owned_joker(int owned_joker_idx);
-8
View File
@@ -23,14 +23,6 @@ void game_shop_reset(void);
*/
JokerObject* game_shop_get_description_card(void);
/**
* @brief Set whether a Joker can appear in the shop.
*
* @param avail - true to make it available to the shop to appear in
* false to make it unavailable.
*/
void game_shop_set_joker_avail(int joker_id, bool avail);
/**
* @brief Change to the shop background
*/
+173
View File
@@ -0,0 +1,173 @@
/**
* @file item.h
*
* @brief The core structure for items in the shop and inventory.
* Provides a common API for the shop and inventory to handle all types of items.
* Uses struct inheritance so all inherited items can implement an is-a relationship with Item.
* This means that pointers to structs that inherit Item using first member struct inheritance
* can and should be cast to Item* so code that expects an Item* can use them.
*/
#ifndef ITEM_H
#define ITEM_H
#include "mgba_logger.h"
#include "sprite.h"
// TODO: Merge these with GBAL_RETURN_IF_NULL macros?
/**
* @brief Checks if @p item is of type @p expected_type - logs error message and returns otherwise
*
* This version is for a function that returns a value, while
* @ref ITEM_RETURN_IF_UNEXPECTED_TYPE_VOID is for a void function.
*/
#define ITEM_RETURN_IF_UNEXPECTED_TYPE_RET(item, expected_type, ret_val) \
do \
{ \
if ((item)->type != expected_type) \
{ \
MGBA_FUNC_ERROR("Unexpected %s->type != %s", #item, #expected_type); \
return (ret_val); \
} \
} while (0)
/**
* @brief Checks if @p item is of type @p expected_type - logs error message and returns otherwise
*
* This version is for a void function, while @ref ITEM_RETURN_IF_UNEXPECTED_TYPE_RET is for one
* with a return value.
*/
#define ITEM_RETURN_IF_UNEXPECTED_TYPE_VOID(item, expected_type) \
do \
{ \
if ((item)->type != expected_type) \
{ \
MGBA_FUNC_ERROR("Unexpected %s->type != %s", #item, #expected_type); \
return; \
} \
} while (0)
enum ItemType
{
ITEM_TYPE_JOKER,
ITEM_TYPE_PLAYING_CARD,
// Future planned item types
// ITEM_TYPE_CONSUMABLE, // Expand to PLANET, TAROT, and SPECTRAL?
// ITEM_TYPE_VOUCHER,
// ITEM_TYPE_PACK
ITEM_NUM_TYPES
};
/**
* @brief A generic interface for all items that can appear in the shop or be in the inventory.
* This uses first member struct inheritance - other structs are meant to inherit it by
* making their first member field Item.
* Then casts from inheriting structs to Item* are allowed and intentional and this allows for
* generic code that uses polymorphism.
* The -fms-extensions compile flag allows for anonymous members making it behave fully
* as inheritance. It makes all member fields be fully inherited so any struct
* that inherits Item for example will have all its fields accessible directly,
* e.g. `JokerObject joker_object; joker_object.type = ITEM_TYPE_JOKER`
*/
typedef struct Item
{
/**
* @brief First member struct inheritance
* all items that can appear in the shop are SpriteObjects.
* Note that this is an anonymous member.
*/
SpriteObject;
/**
* @brief The item type - used to dispatch the function implementations for inheriting types.
*/
enum ItemType type;
} Item;
/**
* @brief The set of functions that each item type implements.
*/
typedef struct ItemFuncs
{
/**
* All items must implement the following since they are called by the shop and all items
* must be capable of appearing in the shop.
*/
Item* (*roll_new)(void);
int (*get_buy_price)(Item* item);
bool (*can_acquire)(Item* item);
void (*acquire)(Item* item);
void (*dispose)(Item** item);
// TODO: void (*print_description)(Item* item); // or something of the form
// Optional implementation functions will be added here
} ItemFuncs;
/**
* @brief Rolls a random item of type @p item_type and returns a newly created one.
* Manages rollable items set if necessary (i.e. not rolling items already in inventory)
* To be used when rolling new items for the shop or packs.
*
* Matches @ref ItemFuncs.roll_new()
*
* @param item_type The type of the item to roll
*
* @return The newly created randomly rolled item
*/
Item* item_roll_new(enum ItemType item_type);
/**
* @brief Returns the buy price of the item.
*
* Matches @ref ItemFuncs.get_buy_price()
*
* @param item The item whose price to return.
*
* @return UNDEFINED in case of error, the item's buy price otherwise.
*/
int item_get_buy_price(Item* item);
/**
* @brief Acquires the item, adding to inventory if applicable.
* Called when it is purchased from the shop, note that it does not
* perform the purchase operation of decrementing the player's money,
* that should be handled by the shop code.
* For packs this can be to just open the pack,
* for vouchers, this will apply their effect.
*
* Matches @ref ItemFuncs.acquire()
*
* @param item The item to acquire
*/
void item_acquire(Item* item);
/**
* @brief Returns true if the item can be acquired, i.e. added to inventory.
* Does not check if the player has enough money to buy the item, that is the shop's job,
* as this will be used both when purchasing and when selecting in a pack.
*
* Matches @ref ItemFuncs.can_acquire()
*
* @param item The item to check
*/
bool item_can_acquire(Item* item);
/**
* @brief Destroys an item, freeing underlying resources, and manages rollable items sets if needed.
* To be used when destroying items from the inventory, shop, or packs.
*
* @param item A pointer to an item for destruction.
*/
void item_dispose(Item** item);
/**
* @brief Prints the buy price under the item
* Relies on the fact item is a SpriteObject
*
* @param item The item to print under
*/
void item_print_buy_price_under(Item* item);
#endif // ITEM_H
+26
View File
@@ -0,0 +1,26 @@
/**
* @file item_funcs.h
*
* @brief An API for the item functions defined in item.h
* Separated module from item.c/.h so item_funcs.c can include joker.h and card.h etc.
* so the item.h doesn't depend on implementations such as jokers and cards.
*/
#ifndef ITEM_FUNCS_H
#define ITEM_FUNCS_H
#include "item.h"
/**
* @brief Returns the function table for a given item type.
*
* Looks up the @ref ItemFuncs dispatch table corresponding to @p type.
*
* @param type The item type whose function table to retrieve.
*
* @return Pointer to the @ref ItemFuncs table for @p type,
* or NULL if @p type is invalid or has no implemented functions.
*/
ItemFuncs* get_item_type_funcs(enum ItemType type);
#endif // ITEM_FUNCS_H
+47 -2
View File
@@ -9,7 +9,7 @@
#include "card.h"
#include "game.h"
#include "graphic_utils.h"
#include "sprite.h"
#include "item.h"
#include <maxmod.h>
@@ -112,8 +112,8 @@ typedef struct
typedef struct JokerObject
{
Item; // First member struct inheritance
Joker* joker;
SpriteObject* sprite_object;
} JokerObject;
typedef struct // These jokers are triggered after the played hand has finished scoring.
@@ -197,6 +197,51 @@ JokerObject* joker_object_new(Joker* joker);
void joker_object_destroy(JokerObject** joker_object);
// This doesn't actually score anything, it just performs an animation and plays a sound effect
void joker_object_shake(JokerObject* joker_object, mm_word sound_id);
/**
* @brief Returns the buy price of the joker object.
*
* @param joker_object the joker object whose price to return.
*
* @return UNDEFINED in case of error, the buy price of the joker otherwise.
*/
int joker_object_get_buy_price(Item* joker_object);
// TODO: Move to an owned_jokers.c/.h file?
/**
* @brief Add a Joker to the list of owned Jokers and place it in the joker row.
*
* @param joker_object The JokerObject to add cast to Item*
*/
void joker_object_add_to_owned(Item* joker_object);
/**
* @brief Destroy a JokerObject item, free its resources, and make it available to be rolled.
*
* @param joker_object Pointer to the JokerObject Item* to destroy; set to NULL.
*/
void joker_object_dispose(Item** joker_object);
/**
* @brief Set whether a Joker is available to be rolled for the shop, packs, etc.
*
* @param joker_id The ID of the joker whose availability to set.
* @param rollable true to make it rollable, false otherwise.
*/
void joker_set_rollable(int joker_id, bool rollable);
/**
* @brief Reset rollable jokers to include all jokers in the registry.
*/
void joker_reset_rollable_jokers(void);
/**
* @brief Roll and create a new JokerObject item.
*
* @return Newly created `Item*` (JokerObject) or NULL if none available.
*/
Item* joker_object_roll_new(void);
// This scores the joker and returns true if it was scored successfully
// card_object = NULL means the joker_event does not concern a particular Card, i.e. Independend or
// On_Blind_Selected as opposed to events that concern a particular card, i.e. On_Card_Scored or
+9 -8
View File
@@ -194,23 +194,24 @@ void sprite_init(void);
void sprite_draw(void);
/**
* @brief Allocate and retrieve a pointer to a valid SpriteObject
* @brief Initialize a SpriteObject to a default state.
* Must be called only once per SpriteObject when it is created.
*
* @return A valid pointer to an newly allocated SpriteObject
* if successful, othewise return **NULL**.
* @param sprite_object - The SpriteObject to initialize
*/
SpriteObject* sprite_object_new();
void sprite_object_init(SpriteObject* sprite_object);
/**
* @brief Destroy SpriteObject
*
* Destroy a SpriteObject by freeing it back to the pool and releasing its
* associated resources
* Destroy a SpriteObject by releasing its associated resources (e.g. the sprite).
* This invalidates the SpriteObject and it should not be used after destroyed,
* a new one should be created instead.
*
* @param sprite_object pointer to a pointer of SpriteObject to destroy.
* @param sprite_object pointer to a SpriteObject to destroy.
* Cannot be **NULL**.
*/
void sprite_object_destroy(SpriteObject** sprite_object);
void sprite_object_destroy(SpriteObject* sprite_object);
/**
* @brief Register a Sprite to an associated SpriteObject