Merge branch 'main' into jokers_with_graphics
This commit is contained in:
@@ -38,7 +38,7 @@ GRAPHICS := graphics
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -mthumb -mthumb-interwork
|
||||
|
||||
CFLAGS := -O3 -Wall -Werror\
|
||||
CFLAGS := -g -O3 -Wall -Werror\
|
||||
-mcpu=arm7tdmi -mtune=arm7tdmi \
|
||||
-ffast-math -fomit-frame-pointer -funroll-loops \
|
||||
$(ARCH)
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
# Balatro GBA
|
||||
This is an attempt to recreate the game 'Balatro' as accurately as possible, including all of the visual effects that make Balatro feel satisfying to play.
|
||||
|
||||
This port is strictly limited to the content that was available in the January 2024 Balatro Steam demo.
|
||||
### **Disclaimer: This project is NOT endorsed or supported by Playstack or LocalThunk**
|
||||
#### This project is solely aimed to recreate Balatro's old demo on the Game Boy Advance and is not meant to infringe or draw sales away from the full game's release or any of the established works by Playstack and LocalThunk. All rights are reserved to their respective holders.
|
||||
This port is strictly limited in content to a minimal version of Balatro and will not recreate the full game.
|
||||
### **Disclaimer: This project is NOT endorsed by or affiliated with Playstack or LocalThunk**
|
||||
#### This is a non-profit community fan project solely aimed to recreate a minimal version of Balatro on the Game Boy Advance as a tribute to the full Balatro and is not meant to infringe or draw sales away from the full game's release or any of the established works by Playstack and LocalThunk.
|
||||
#### All rights are reserved to their respective holders.
|
||||
This version is meant for people who already own and know the official full game.
|
||||
|
||||
Buy the official full version
|
||||
|
||||
[Balatro on Steam](https://store.steampowered.com/app/2379780/Balatro/)
|
||||
|
||||
[Balatro on Google Play](https://play.google.com/store/apps/details?id=com.playstack.balatro.android)
|
||||
|
||||
[Balatro on Apple App Store](https://apps.apple.com/us/app/balatro/id6502453075)
|
||||
|
||||
[Balatro on Switch](https://www.nintendo.com/us/store/products/balatro-switch/)
|
||||
|
||||
[Balatro on PlayStation Store](https://store.playstation.com/en-us/concept/10010334)
|
||||
|
||||
[Balatro on Xbox](https://www.xbox.com/games/store/balatro/9PK087LNGJC5)
|
||||
|
||||

|
||||
|
||||
# **Build Instructions:**
|
||||
@@ -22,7 +39,7 @@ Example: `cd '/home/user/OneDrive/Documents/balatro-gba` or something like this.
|
||||
9. After it completes, navigate through the `build` directory in the project folder and look for `balatro-gba.gba` and load it on an emulator or flashcart.
|
||||
|
||||
## **-Linux-**
|
||||
[Not Available]
|
||||
[No Instructions Available]
|
||||
|
||||
### **Common Issues:**
|
||||
1. **When I drag the folder into the window and press enter, it doesn't work!**
|
||||
@@ -36,5 +53,3 @@ Example: `cd '/home/user/OneDrive/Documents/balatro-gba` or something like this.
|
||||
5. **It says I don't have `Git` or `Make` installed!**
|
||||
-Use `pacman -S git` or `pacman -S make` although it should already be installed if you followed the instructions correctly.
|
||||
|
||||
#### Purchasing the official game is highly recommended and almost mandatory in order for this project to have a chance of staying up due to copyright issues and potential legal action.
|
||||
**Purchase the Steam Version here: https://store.steampowered.com/app/2379780/Balatro/**
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
+7
-1
@@ -6,10 +6,16 @@
|
||||
#include "sprite.h"
|
||||
#include "card.h"
|
||||
#include "game.h"
|
||||
#include "graphic_utils.h"
|
||||
|
||||
#define JOKER_TID (MAX_HAND_SIZE + MAX_SELECTION_SIZE) * JOKER_SPRITE_OFFSET // Tile ID for the starting index in the tile memory
|
||||
#define JOKER_SPRITE_OFFSET 16 // Offset for the joker sprites
|
||||
#define JOKER_BASE_PB 4 // Palette bank for the default jokers
|
||||
#define JOKER_BASE_PB 4 // The starting palette index for the jokers
|
||||
#define JOKER_LAST_PB (NUM_PALETTES - 1)
|
||||
// Currently allocating the rest of the palettes for the jokers.
|
||||
// This number needs to be decreased once we need to allocated palettes for other sprites
|
||||
// such as planet cards etc.
|
||||
|
||||
#define JOKER_STARTING_LAYER 27
|
||||
|
||||
#define BASE_EDITION 0
|
||||
|
||||
+3
-1
@@ -4,11 +4,12 @@
|
||||
#include <tonc.h>
|
||||
#include <maxmod.h>
|
||||
|
||||
#define CARD_SPRITE_SIZE 32
|
||||
|
||||
typedef struct
|
||||
{
|
||||
OBJ_ATTR *obj;
|
||||
OBJ_AFFINE *aff;
|
||||
u32 tid, pb;
|
||||
POINT pos;
|
||||
} Sprite;
|
||||
|
||||
@@ -44,6 +45,7 @@ INLINE void sprite_position(Sprite *sprite, int x, int y)
|
||||
// Sprite functions
|
||||
void sprite_init();
|
||||
void sprite_draw();
|
||||
int sprite_get_pb(const Sprite* sprite);
|
||||
|
||||
// SpriteObject methods
|
||||
SpriteObject *sprite_object_new();
|
||||
|
||||
+37
-32
@@ -1836,7 +1836,7 @@ static void game_shop_create_items(JokerObject *shop_jokers[], bool first_time)
|
||||
shop_jokers[i]->sprite_object->ty = int2fx(71);
|
||||
|
||||
int x = fx2int(shop_jokers[i]->sprite_object->tx) + TILE_SIZE - (get_digits_even(shop_jokers[i]->joker->value) - 1) * TILE_SIZE;
|
||||
int y = fx2int(shop_jokers[i]->sprite_object->ty);
|
||||
int y = fx2int(shop_jokers[i]->sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE;
|
||||
tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, shop_jokers[i]->joker->value);
|
||||
|
||||
if (first_time == false)
|
||||
@@ -2150,6 +2150,39 @@ void game_round_end()
|
||||
}
|
||||
}
|
||||
|
||||
void game_shop_intro(JokerObject* shop_jokers[])
|
||||
{
|
||||
main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP);
|
||||
|
||||
if (timer == 1)
|
||||
{
|
||||
game_shop_create_items(shop_jokers, true);
|
||||
}
|
||||
|
||||
if (timer >= 7) // Shift the shop icon
|
||||
{
|
||||
int timer_offset = timer - 6;
|
||||
|
||||
// TODO: Extract to generic function?
|
||||
for (int y = 0; y < timer_offset; y++)
|
||||
{
|
||||
int y_from = 26 + y - timer_offset;
|
||||
int y_to = 0 + y;
|
||||
|
||||
Rect from = { 0, y_from, 8, y_from };
|
||||
BG_POINT to = { 0, y_to };
|
||||
|
||||
main_bg_se_copy_rect(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
if (timer == 12)
|
||||
{
|
||||
state = 1;
|
||||
timer = 0; // Reset the timer
|
||||
}
|
||||
}
|
||||
|
||||
void game_shop()
|
||||
{
|
||||
change_background(BG_ID_SHOP);
|
||||
@@ -2198,36 +2231,7 @@ void game_shop()
|
||||
{
|
||||
case 0: // Intro sequence (menu and shop icon coming into frame)
|
||||
{
|
||||
main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP);
|
||||
|
||||
if (timer == 1)
|
||||
{
|
||||
game_shop_create_items(shop_jokers, true);
|
||||
}
|
||||
|
||||
if (timer >= 7) // Shift the shop icon
|
||||
{
|
||||
int timer_offset = timer - 6;
|
||||
|
||||
// TODO: Extract to generic function?
|
||||
for (int y = 0; y < timer_offset; y++)
|
||||
{
|
||||
int y_from = 26 + y - timer_offset;
|
||||
int y_to = 0 + y;
|
||||
|
||||
Rect from = {0, y_from, 8, y_from};
|
||||
BG_POINT to = {0, y_to};
|
||||
|
||||
main_bg_se_copy_rect(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
if (timer == 12)
|
||||
{
|
||||
state = 1;
|
||||
timer = 0; // Reset the timer
|
||||
}
|
||||
|
||||
game_shop_intro(shop_jokers);
|
||||
break;
|
||||
}
|
||||
case 1: // Shop menu input and selection
|
||||
@@ -2329,7 +2333,8 @@ void game_shop()
|
||||
|
||||
Rect joker_price_rect;
|
||||
joker_price_rect.left = fx2int(shop_jokers[i]->sprite_object->tx);
|
||||
joker_price_rect.top = fx2int(shop_jokers[i]->sprite_object->ty) + TILE_SIZE;
|
||||
// 2*TILE_SIZE because the item is highlighted and raised by 1 additional tile
|
||||
joker_price_rect.top = fx2int(shop_jokers[i]->sprite_object->ty) + CARD_SPRITE_SIZE + 2*TILE_SIZE;
|
||||
joker_price_rect.right = joker_price_rect.left + TILE_SIZE * 3;
|
||||
joker_price_rect.bottom = joker_price_rect.top + TILE_SIZE;
|
||||
|
||||
|
||||
+49
-20
@@ -39,16 +39,52 @@ static bool used_layers[MAX_JOKER_OBJECTS] = {false}; // Track used layers for j
|
||||
// Maps the spritesheet index to the palette bank index allocated to it.
|
||||
// Spritesheets that were not allocated are
|
||||
static int* joker_spritesheet_pb_map;
|
||||
static int joker_pb_num_sprite_users[JOKER_LAST_PB - JOKER_BASE_PB + 1] = { 0 };
|
||||
|
||||
static int get_num_spritesheets()
|
||||
{
|
||||
return (get_joker_registry_size() + NUM_JOKERS_PER_SPRITESHEET - 1) / NUM_JOKERS_PER_SPRITESHEET;
|
||||
}
|
||||
|
||||
static int joker_get_spritesheet_idx(u8 joker_id)
|
||||
|
||||
{
|
||||
return joker_id / NUM_JOKERS_PER_SPRITESHEET;
|
||||
}
|
||||
|
||||
// TODO: This should be generalized so any sprite can have dynamic swapping
|
||||
static void joker_pb_add_sprite_user(int pb)
|
||||
{
|
||||
joker_pb_num_sprite_users[pb - JOKER_BASE_PB]++;
|
||||
}
|
||||
|
||||
static void joker_pb_remove_sprite_user(int pb)
|
||||
{
|
||||
int num_sprite_users = joker_pb_num_sprite_users[pb - JOKER_BASE_PB];
|
||||
joker_pb_num_sprite_users[pb - JOKER_BASE_PB] = max(0, num_sprite_users - 1);
|
||||
}
|
||||
|
||||
static int joker_pb_get_num_sprite_users(int joker_pb)
|
||||
{
|
||||
return joker_pb_num_sprite_users[joker_pb - JOKER_BASE_PB];
|
||||
}
|
||||
|
||||
static int get_unused_joker_pb()
|
||||
{
|
||||
for (int i = 0; i < NUM_ELEM_IN_ARR(joker_pb_num_sprite_users); i++)
|
||||
{
|
||||
if (joker_pb_num_sprite_users[i] == 0)
|
||||
{
|
||||
return (i + JOKER_BASE_PB);
|
||||
}
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
static int allocate_pb_if_needed(u8 joker_id)
|
||||
{
|
||||
int joker_spritesheet_idx = joker_id / NUM_JOKERS_PER_SPRITESHEET;
|
||||
int joker_spritesheet_idx = joker_get_spritesheet_idx(joker_id);
|
||||
int joker_pb = joker_spritesheet_pb_map[joker_spritesheet_idx];
|
||||
if (joker_pb != UNDEFINED)
|
||||
{
|
||||
@@ -57,23 +93,9 @@ static int allocate_pb_if_needed(u8 joker_id)
|
||||
}
|
||||
|
||||
// Allocate a new palette
|
||||
joker_pb = int_arr_max(joker_spritesheet_pb_map, get_num_spritesheets());
|
||||
joker_pb = get_unused_joker_pb();
|
||||
|
||||
if (joker_pb == UNDEFINED)
|
||||
{
|
||||
// This is the first palette loaded
|
||||
joker_pb = JOKER_BASE_PB;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The new palette bank is a 1 increment of the last allocated one
|
||||
/* TODO: This doesn't account for removal, we need to address that,
|
||||
* track sprite usage and remove palettes for unused sprites
|
||||
*/
|
||||
joker_pb = joker_pb + 1;
|
||||
}
|
||||
|
||||
if (joker_pb == NUM_PALETTES)
|
||||
{
|
||||
// Ran out of palettes, default to base and pray
|
||||
joker_pb = JOKER_BASE_PB;
|
||||
@@ -137,7 +159,7 @@ Joker *joker_new(u8 id)
|
||||
|
||||
joker->id = id;
|
||||
joker->modifier = BASE_EDITION; // TODO: Make this a parameter
|
||||
joker->value = jinfo->base_value + edition_price_lut[joker->modifier]; // Base value + edition price
|
||||
joker->value = jinfo->base_value + edition_price_lut[joker->modifier];
|
||||
joker->rarity = jinfo->rarity;
|
||||
joker->processed = false;
|
||||
|
||||
@@ -180,12 +202,13 @@ JokerObject *joker_object_new(Joker *joker)
|
||||
|
||||
int tile_index = JOKER_TID + (layer * JOKER_SPRITE_OFFSET);
|
||||
|
||||
int joker_spritesheet_idx = joker->id / NUM_JOKERS_PER_SPRITESHEET;
|
||||
int joker_spritesheet_idx = joker_get_spritesheet_idx(joker->id);
|
||||
int joker_idx = joker->id % NUM_JOKERS_PER_SPRITESHEET;
|
||||
int joker_pb = allocate_pb_if_needed(joker->id);
|
||||
joker_pb_add_sprite_user(joker_pb);
|
||||
|
||||
memcpy32(&tile_mem[4][tile_index], &joker_gfxTiles[joker_spritesheet_idx][joker_idx * TILE_SIZE * JOKER_SPRITE_OFFSET], TILE_SIZE * JOKER_SPRITE_OFFSET);
|
||||
|
||||
|
||||
sprite_object_set_sprite
|
||||
(
|
||||
joker_object->sprite_object,
|
||||
@@ -206,10 +229,16 @@ JokerObject *joker_object_new(Joker *joker)
|
||||
|
||||
void joker_object_destroy(JokerObject **joker_object)
|
||||
{
|
||||
if (*joker_object == NULL) return;
|
||||
if (joker_object == NULL || *joker_object == NULL) return;
|
||||
|
||||
int layer = sprite_get_layer(joker_object_get_sprite(*joker_object)) - JOKER_STARTING_LAYER;
|
||||
used_layers[layer] = false;
|
||||
joker_pb_remove_sprite_user(sprite_get_pb(joker_object_get_sprite(*joker_object)));
|
||||
if (joker_pb_get_num_sprite_users((sprite_get_pb(joker_object_get_sprite(*joker_object)))) == 0)
|
||||
{
|
||||
joker_spritesheet_pb_map[joker_get_spritesheet_idx((*joker_object)->joker->id)] = UNDEFINED;
|
||||
}
|
||||
|
||||
sprite_object_destroy(&(*joker_object)->sprite_object); // Destroy the sprite
|
||||
joker_destroy(&(*joker_object)->joker); // Destroy the joker
|
||||
free(*joker_object);
|
||||
|
||||
+11
-2
@@ -1,5 +1,6 @@
|
||||
#include "sprite.h"
|
||||
#include "util.h"
|
||||
#include "audio_utils.h"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <stdlib.h>
|
||||
@@ -100,6 +101,15 @@ void sprite_draw()
|
||||
oam_copy(oam_mem, obj_buffer, MAX_SPRITES);
|
||||
}
|
||||
|
||||
int sprite_get_pb(const Sprite *sprite)
|
||||
{
|
||||
if (sprite == NULL)
|
||||
{
|
||||
return UNDEFINED;
|
||||
}
|
||||
return (sprite->obj->attr2 & ATTR2_PALBANK_MASK) >> ATTR2_PALBANK_SHIFT;
|
||||
}
|
||||
|
||||
// SpriteObject methods
|
||||
SpriteObject* sprite_object_new()
|
||||
{
|
||||
@@ -206,8 +216,7 @@ void sprite_object_shake(SpriteObject* sprite_object, mm_word sound_id)
|
||||
|
||||
if (sound_id == UNDEFINED) return; // If no sound ID is provided, do nothing
|
||||
|
||||
mm_sound_effect sfx_select = { {sound_id}, 1024, 0, 255, 128, };
|
||||
mmEffectEx(&sfx_select);
|
||||
play_sfx(sound_id, MM_BASE_PITCH_RATE);
|
||||
}
|
||||
|
||||
void sprite_object_set_selected(SpriteObject* sprite_object, bool selected)
|
||||
|
||||
Reference in New Issue
Block a user