(Manual conflict res) Merge branch 'main' into ttm/joker-scope-joker-1

This commit is contained in:
MeirGavish
2025-08-15 10:56:41 +03:00
32 changed files with 640 additions and 314 deletions
+46
View File
@@ -0,0 +1,46 @@
#include "affine_background.h"
#include "affine_background_gfx.h"
#include "graphic_utils.h"
BG_AFFINE bgaff;
AFF_SRC_EX asx = {32<<8, 64<<8, 120, 80, 0x0100, 0x0100, 0};
void affine_background_init()
{
memcpy32_tile8_with_palette_offset((u32*)&tile8_mem[AFFINE_BG_CBB], (const u32*)affine_background_gfxTiles, affine_background_gfxTilesLen/4, AFFINE_BG_PB);
GRIT_CPY(&se_mem[AFFINE_BG_SBB], affine_background_gfxMap);
affine_background_load_palette(affine_background_gfxPal);
bgaff = bg_aff_default;
}
void affine_background_update()
{
static uint timer = 0;
timer++;
// These values are not permament, just the current configuration for the affine background
asx.tex_x += 5;
asx.tex_y += 12;
asx.sx = (lu_sin(timer * 100)) >> 8; // Scale the sine value to fit in a s16
asx.sx += 256; // Add 256 to the sine value to make it positive
asx.sy = (lu_sin(timer * 100 + 0x4000)) >> 8; // Scale the sine value to fit in a s16
asx.sy += 256; // Add 256 to the sine value to make it positive
bg_rotscale_ex(&bgaff, &asx);
REG_BG_AFFINE[2] = bgaff;
}
void affine_background_set_color(COLOR color)
{
memcpy16(&pal_bg_mem[AFFINE_BG_PB], affine_background_gfxPal, AFFINE_BG_PAL_LEN);
for (int i = 0; i < AFFINE_BG_PAL_LEN; i++)
{
clr_rgbscale(&pal_bg_mem[AFFINE_BG_PB] + i, &pal_bg_mem[AFFINE_BG_PB] + i, 1, color);
}
}
void affine_background_load_palette(const u16 *src)
{
memcpy16(&pal_bg_mem[AFFINE_BG_PB], src, AFFINE_BG_PAL_LEN);
}
+282 -216
View File
@@ -11,6 +11,7 @@
#include "hand_analysis.h"
#include "blind.h"
#include "joker.h"
#include "affine_background.h"
#include "graphic_utils.h"
#include "tonc_video.h"
#include "audio_utils.h"
@@ -18,9 +19,12 @@
#include "background_gfx.h"
#include "background_shop_gfx.h"
#include "background_blind_select_gfx.h"
#include "affine_background_gfx.h"
#include "soundbank.h"
#include "list.h"
// This seed system is temporary.
static uint rng_seed = 2048;
static bool seeded = false;
@@ -148,6 +152,11 @@ int get_hand_top(void) {
return hand_top;
}
int hand_get_size(void)
{
return hand_top + 1;
}
CardObject **get_played_array(void) {
return played;
}
@@ -176,6 +185,8 @@ int get_money(void) {
return money;
}
IntList* jokers_available_to_shop; // List of joker IDs
// Consts
// Rects left top right bottom
@@ -440,6 +451,8 @@ void change_background(int id)
else if (current_blind == BOSS_BLIND)
{
main_bg_se_copy_rect(BOSS_BLIND_TITLE_SRC_RECT, TOP_LEFT_BLIND_TITLE_POINT);
affine_background_set_color(blind_get_color(BOSS_BLIND, BLIND_SHADOW_COLOR_INDEX));
}
bg_copy_current_item_to_top_left_panel();
@@ -861,11 +874,6 @@ void hand_change_sort()
sort_cards();
}
int hand_get_size()
{
return hand_top + 1;
}
int hand_get_max_size()
{
return hand_size;
@@ -1002,8 +1010,21 @@ void game_set_state(enum GameState new_game_state)
game_state = new_game_state;
}
void jokers_avialable_to_shop_init()
{
int num_defined_jokers = get_joker_registry_size();
jokers_available_to_shop = int_list_new(num_defined_jokers);
for (int i = 0; i < num_defined_jokers; i++)
{
// Add all joker IDs sequentially
int_list_append(jokers_available_to_shop, i);
}
}
void game_init()
{
jokers_avialable_to_shop_init();
hands = max_hands;
discards = max_discards;
@@ -1563,7 +1584,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
FIXED played_x = int2fx(120);
FIXED played_y = int2fx(70);
FIXED played_scale = float2fx(1.0f);
FIXED played_scale = FIX_ONE;
played_x = played_x + (int2fx(played_top - i) - int2fx(played_top) / 2) * -27;
@@ -1627,7 +1648,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
tte_set_special(0xD000); // Set text color to blue from background memory
// Write the score to a character buffer variable
char score_buffer[5]; // Assuming the maximum score is 99, we need 4 characters (2 digits + null terminator)
char score_buffer[INT_MAX_DIGITS + 2]; // for '+' and null terminator
snprintf(score_buffer, sizeof(score_buffer), "+%d", card_get_value(played[j]->card));
tte_write(score_buffer);
@@ -1822,41 +1843,6 @@ static void game_round_end_cashout()
display_score(score); // Set the score display
}
static void game_shop_create_items(JokerObject *shop_jokers[], bool first_time)
{
tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT);
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
joker_object_destroy(&shop_jokers[i]); // Destroy the joker object if it exists
}
u8 joker_id = random() % get_joker_registry_size();
// TODO: don't pick a joker that we already own
// TODO: weight the random choice by joker rarity
shop_jokers[i] = joker_object_new(joker_new(joker_id));
shop_jokers[i]->sprite_object->x = int2fx(120 + i * 32);
shop_jokers[i]->sprite_object->y = int2fx(160);
shop_jokers[i]->sprite_object->tx = shop_jokers[i]->sprite_object->x;
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);
tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, shop_jokers[i]->joker->value);
if (first_time == false)
{
shop_jokers[i]->sprite_object->y = shop_jokers[i]->sprite_object->ty; // If it's not the first time, set the y position to the target position
joker_object_shake(shop_jokers[i], UNDEFINED); // Give the joker a little wiggle animation
}
sprite_position(joker_object_get_sprite(shop_jokers[i]), fx2int(shop_jokers[i]->sprite_object->x), fx2int(shop_jokers[i]->sprite_object->y));
}
}
void game_playing()
{
// TODO: Blind rect sliding into view animation...
@@ -1998,6 +1984,7 @@ void game_round_end()
tte_erase_rect_wrapper(BLIND_REWARD_RECT);
tte_erase_rect_wrapper(BLIND_REQ_TEXT_RECT);
obj_hide(playing_blind_token->obj);
affine_background_load_palette(affine_background_gfxPal);
state = 5;
timer = 0;
}
@@ -2158,6 +2145,245 @@ void game_round_end()
}
}
static void game_shop_create_items(JokerObject *shop_jokers[])
{
tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT);
if (int_list_get_size(jokers_available_to_shop) == 0)
{
// No jokers to create
return;
}
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
// TODO: weight the random choice by joker rarity
int joker_idx = random() % int_list_get_size(jokers_available_to_shop);
int joker_id = int_list_get(jokers_available_to_shop, joker_idx);
int_list_remove_by_idx(jokers_available_to_shop, joker_idx);
shop_jokers[i] = joker_object_new(joker_new(joker_id));
shop_jokers[i]->sprite_object->x = int2fx(120 + i * 32);
shop_jokers[i]->sprite_object->y = int2fx(160);
shop_jokers[i]->sprite_object->tx = shop_jokers[i]->sprite_object->x;
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) + CARD_SPRITE_SIZE + TILE_SIZE;
tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, shop_jokers[i]->joker->value);
sprite_position(joker_object_get_sprite(shop_jokers[i]), fx2int(shop_jokers[i]->sprite_object->x), fx2int(shop_jokers[i]->sprite_object->y));
}
}
// Intro sequence (menu and shop icon coming into frame)
static 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);
}
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
}
}
static void game_shop_reroll(JokerObject** shop_jokers, int *reroll_cost)
{
money -= *reroll_cost;
display_money(money); // Update the money display
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
int_list_append(jokers_available_to_shop, shop_jokers[i]->joker->id);
joker_object_destroy(&shop_jokers[i]); // Destroy the joker object if it exists
}
}
game_shop_create_items(shop_jokers);
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
shop_jokers[i]->sprite_object->y = shop_jokers[i]->sprite_object->ty; // Set the y position to the target position
joker_object_shake(shop_jokers[i], UNDEFINED); // Give the joker a little wiggle animation
}
}
(*reroll_cost)++;
tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, *reroll_cost);
}
// Shop menu input and selection
static void game_shop_process_user_input(JokerObject** shop_jokers)
{
static const int REROLL_BASE_COST = 5; // Base cost for rerolling the shop items
static int reroll_cost = REROLL_BASE_COST;
// temp variables for future implementation
const ushort max_items_top = MAX_SHOP_JOKERS;
const ushort max_items_bottom = 0;
if (timer == 1)
{
tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, reroll_cost);
}
// Shop input logic
if (key_hit(KEY_UP))
{
selection_y = 0;
if (selection_x > max_items_top)
{
selection_x = max_items_top;
}
}
else if (key_hit(KEY_DOWN))
{
selection_y = 1;
if (selection_x > max_items_bottom)
{
selection_x = max_items_bottom;
}
}
else if (key_hit(KEY_LEFT))
{
if (selection_x > 0)
{
selection_x--;
}
}
else if (key_hit(KEY_RIGHT))
{
// TODO: Don't allow cursor on sold items
if (selection_y == 0 && selection_x < max_items_top)
{
selection_x++;
}
else if (selection_y == 1 && selection_x < max_items_bottom)
{
selection_x++;
}
}
memcpy16(&pal_bg_mem[7], &pal_bg_mem[3], 1);
memcpy16(&pal_bg_mem[5], &pal_bg_mem[16], 1);
// Shop selection logic
if (selection_x == 0 && selection_y == 0) // Next round button
{
memset16(&pal_bg_mem[5], 0xFFFF, 1);
if (key_hit(SELECT_CARD))
{
// Go to next blind selection game state
state = 2; // Go to the outro sequence state
timer = 0; // Reset the timer
reroll_cost = REROLL_BASE_COST;
memcpy16(&pal_bg_mem[5], &pal_bg_mem[6], 1);
// memcpy16(&pal_bg_mem[16], &pal_bg_mem[6], 1);
// This changes the color of the button to a dark red.
// However, it shares a palette with the shop icon, so it will change the color of the shop icon as well.
// And I don't care enough to fix it right now.
}
}
else if (selection_x == 0 && selection_y == 1) // Reroll button
{
memset16(&pal_bg_mem[7], 0xFFFF, 1);
if (key_hit(SELECT_CARD) && money >= reroll_cost)
{
game_shop_reroll(shop_jokers, &reroll_cost);
}
}
for (int i = 0; i < MAX_SHOP_JOKERS; i++) // Item selection logic
{
if (shop_jokers[i] != NULL)
{
if (i == selection_x - 1 && selection_y == 0)
{
shop_jokers[i]->sprite_object->ty = int2fx(61);
if (key_hit(SELECT_CARD) && jokers_top < MAX_JOKERS_HELD_SIZE - 1 && money >= shop_jokers[i]->joker->value)
{
joker_push(shop_jokers[i]);
money -= shop_jokers[i]->joker->value; // Deduct the money spent on the joker
display_money(money); // Update the money display
Rect joker_price_rect;
joker_price_rect.left = fx2int(shop_jokers[i]->sprite_object->tx);
// 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;
tte_erase_rect_wrapper(joker_price_rect);
shop_jokers[i] = NULL; // Remove the joker from the shop
}
}
else
{
shop_jokers[i]->sprite_object->ty = int2fx(71);
}
}
}
}
static void game_shop_lights_anim_frame()
{
// Shift palette around the border of the shop icon
COLOR shifted_palette[4];
memcpy16(&shifted_palette[0], &pal_bg_mem[14], 1);
memcpy16(&shifted_palette[1], &pal_bg_mem[17], 1);
memcpy16(&shifted_palette[2], &pal_bg_mem[22], 1);
memcpy16(&shifted_palette[3], &pal_bg_mem[8], 1);
// Circularly shift the palette
int last = shifted_palette[3];
for (int i = 3; i > 0; --i)
{
shifted_palette[i] = shifted_palette[i - 1];
}
shifted_palette[0] = last;
memcpy16(&pal_bg_mem[14], &shifted_palette[0], 1); // Copy the shifted palette to the next 4 slots
memcpy16(&pal_bg_mem[17], &shifted_palette[1], 1);
memcpy16(&pal_bg_mem[22], &shifted_palette[2], 1);
memcpy16(&pal_bg_mem[8], &shifted_palette[3], 1);
}
void game_shop()
{
change_background(BG_ID_SHOP);
@@ -2172,187 +2398,21 @@ void game_shop()
}
}
const int reroll_base_cost = 5; // Base cost for rerolling the shop items
static int reroll_cost = reroll_base_cost;
// temp variables for future implementation
const ushort max_items_top = MAX_SHOP_JOKERS;
const ushort max_items_bottom = 0;
if (timer % 20 == 0) // Shift palette around the border of the shop icon
if (timer % 20 == 0)
{
COLOR shifted_palette[4];
memcpy16(&shifted_palette[0], &pal_bg_mem[14], 1);
memcpy16(&shifted_palette[1], &pal_bg_mem[17], 1);
memcpy16(&shifted_palette[2], &pal_bg_mem[22], 1);
memcpy16(&shifted_palette[3], &pal_bg_mem[8], 1);
// Circularly shift the palette
int last = shifted_palette[3];
for (int i = 3; i > 0; --i) {
shifted_palette[i] = shifted_palette[i - 1];
}
shifted_palette[0] = last;
memcpy16(&pal_bg_mem[14], &shifted_palette[0], 1); // Copy the shifted palette to the next 4 slots
memcpy16(&pal_bg_mem[17], &shifted_palette[1], 1);
memcpy16(&pal_bg_mem[22], &shifted_palette[2], 1);
memcpy16(&pal_bg_mem[8], &shifted_palette[3], 1);
game_shop_lights_anim_frame();
}
switch (state) // I'm only using magic numbers here for the sake of simplicity since it's just sequential, but you can replace them with named constants or enums if it makes it clearer
{
case 0: // Intro sequence (menu and shop icon coming into frame)
case 0:
{
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
case 1:
{
if (timer == 1)
{
tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, reroll_cost);
}
// Shop input logic
if (key_hit(KEY_UP))
{
selection_y = 0;
if (selection_x > max_items_top)
{
selection_x = max_items_top;
}
}
else if (key_hit(KEY_DOWN))
{
selection_y = 1;
if (selection_x > max_items_bottom)
{
selection_x = max_items_bottom;
}
}
else if (key_hit(KEY_LEFT))
{
if (selection_x > 0)
{
selection_x--;
}
}
else if (key_hit(KEY_RIGHT))
{
if (selection_y == 0 && selection_x < max_items_top)
{
selection_x++;
}
else if (selection_y == 1 && selection_x < max_items_bottom)
{
selection_x++;
}
}
memcpy16(&pal_bg_mem[7], &pal_bg_mem[3], 1);
memcpy16(&pal_bg_mem[5], &pal_bg_mem[16], 1);
// Shop selection logic
if (selection_x == 0 && selection_y == 0)
{
memset16(&pal_bg_mem[5], 0xFFFF, 1);
if (key_hit(SELECT_CARD))
{
// Go to next blind selection game state
state = 2; // Go to the outro sequence state
timer = 0; // Reset the timer
memcpy16(&pal_bg_mem[5], &pal_bg_mem[6], 1);
// memcpy16(&pal_bg_mem[16], &pal_bg_mem[6], 1);
// This changes the color of the button to a dark red.
// However, it shares a palette with the shop icon, so it will change the color of the shop icon as well.
// And I don't care enough to fix it right now.
}
}
else if (selection_x == 0 && selection_y == 1)
{
memset16(&pal_bg_mem[7], 0xFFFF, 1);
if (key_hit(SELECT_CARD) && money >= reroll_cost)
{
money -= reroll_cost;
display_money(money); // Update the money display
game_shop_create_items(shop_jokers, false);
reroll_cost++;
tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, reroll_cost);
}
}
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
if (i == selection_x - 1 && selection_y == 0)
{
shop_jokers[i]->sprite_object->ty = int2fx(61);
if (key_hit(SELECT_CARD) && jokers_top < MAX_JOKERS_HELD_SIZE - 1 && money >= shop_jokers[i]->joker->value)
{
joker_push(shop_jokers[i]);
money -= shop_jokers[i]->joker->value; // Deduct the money spent on the joker
display_money(money); // Update the money display
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;
joker_price_rect.right = joker_price_rect.left + TILE_SIZE * 3;
joker_price_rect.bottom = joker_price_rect.top + TILE_SIZE;
tte_erase_rect_wrapper(joker_price_rect);
shop_jokers[i] = NULL; // Remove the joker from the shop
}
}
else
{
shop_jokers[i]->sprite_object->ty = int2fx(71);
}
}
}
game_shop_process_user_input(shop_jokers);
break;
}
case 2: // Outro sequence (menu and shop icon going out of frame)
@@ -2400,23 +2460,29 @@ void game_shop()
}
default:
state = 0; // Reset the state
reroll_cost = reroll_base_cost;
selection_x = 0; // Reset the selection
selection_y = 0; // Reset the selection
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
if (shop_jokers[i] != NULL)
{
// Make the joker available back to shop
int_list_append(jokers_available_to_shop, shop_jokers[i]->joker->id);
}
joker_object_destroy(&shop_jokers[i]); // Destroy the joker objects
}
increment_blind(BLIND_DEFEATED);
increment_blind(BLIND_DEFEATED); // TODO: Move to game_round_end()?
game_set_state(GAME_BLIND_SELECT); // If we reach here, we should go to the blind select state
break;
}
}
void game_blind_select()
{
switch (state) // I'm only using magic numbers here for the sake of simplicity since it's just sequential, but you can replace them with named constants or enums if it makes it clearer
+19
View File
@@ -168,3 +168,22 @@ void update_text_rect_to_right_align_num(Rect* rect, int num, int overflow_direc
}
}
void memcpy16_tile8_with_palette_offset(u16* dst, const u16* src, uint hwcount, u8 palette_offset)
{
const u16 offset = (((palette_offset) << 8) | (palette_offset));
for (int i = 0; i < hwcount; i++)
{
// Copying u8 data twice across u16 data
dst[i] = src[i] + offset;
}
}
void memcpy32_tile8_with_palette_offset(u32* dst, const u32* src, uint wcount, u8 palette_offset)
{
const u32 offset = (palette_offset << 24) | (palette_offset << 16) | (palette_offset << 8) | palette_offset;
for (int i = 0; i < wcount; i++)
{
// Copying u8 data 4 times across u32 data
dst[i] = src[i] + offset;
}
}
+84
View File
@@ -0,0 +1,84 @@
#include <stdlib.h>
#include <stdbool.h>
#include "list.h"
#include "util.h"
IntList *int_list_new(int init_size) {
IntList *list = (IntList *)malloc(sizeof(IntList));
if (list == NULL) return NULL;
list->_array = (int *)malloc(sizeof(int) * init_size);
if (!list->_array)
{
free(list);
return NULL;
}
list->size = 0;
list->allocated_size = init_size;
return list;
}
void int_list_destroy(IntList **list) {
if (list == NULL || *list == NULL)
return;
{
free((*list)->_array);
free(*list);
}
*list = NULL;
}
bool int_list_append(IntList *list, int value)
{
if (list->size >= list->allocated_size)
{
int new_size = list->allocated_size * 2;
int *new_arr = (int *)realloc(list->_array, sizeof(int) * new_size);
if (new_arr == NULL)
return false;
list->_array = new_arr;
list->allocated_size = new_size;
}
list->_array[list->size++] = value;
return true;
}
bool int_list_remove_by_idx(IntList *list, int index) {
if (index < 0 || index >= list->size)
return false;
for (int i = index; i < list->size - 1; ++i)
{
list->_array[i] = list->_array[i + 1];
}
list->size--;
return true;
}
bool int_list_remove_by_value(IntList *list, int value)
{
for (int i = 0; i < list->size; i++)
{
if (list->_array[i] == value)
{
return int_list_remove_by_idx(list, i);
}
}
return false;
}
int int_list_get(IntList *list, int index)
{
if (index < 0 || index >= list->size)
return 0;
return list->_array[index];
}
int int_list_get_size(IntList *list)
{
if (list == NULL)
{
return UNDEFINED;
}
return list->size;
}
+66 -28
View File
@@ -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;
@@ -105,6 +127,9 @@ void joker_init()
joker_gfxTiles[6] = joker_gfx6Tiles;
joker_gfxTiles[7] = joker_gfx7Tiles;
joker_gfxTiles[8] = joker_gfx8Tiles;
joker_gfxTiles[9] = joker_gfx9Tiles;
joker_gfxTiles[10] = joker_gfx10Tiles;
joker_gfxTiles[11] = joker_gfx11Tiles;
joker_gfxPal[0] = joker_gfx0Pal;
joker_gfxPal[1] = joker_gfx1Pal;
@@ -115,6 +140,9 @@ void joker_init()
joker_gfxPal[6] = joker_gfx6Pal;
joker_gfxPal[7] = joker_gfx7Pal;
joker_gfxPal[8] = joker_gfx8Pal;
joker_gfxPal[9] = joker_gfx9Pal;
joker_gfxPal[10] = joker_gfx10Pal;
joker_gfxPal[11] = joker_gfx11Pal;
for (int i = 0; i < num_spritesheets; i++)
{
@@ -131,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;
@@ -174,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,
@@ -200,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);
@@ -235,42 +270,45 @@ bool joker_object_score(JokerObject *joker_object, Card* scored_card, int *chips
*money += joker_effect.money;
// TODO: Retrigger
const int joker_score_display_offset_px = (MAX_CARD_SCORE_STR_LEN + 1)*TTE_CHAR_SIZE;
// + 1 For space
int cursorPosX = fx2int(joker_object->sprite_object->x) + 8; // Offset of 16 pixels to center the text on the card
if (joker_effect.chips > 0)
{
char score_buffer[12];
char score_buffer[INT_MAX_DIGITS + 2]; // For '+' and null terminator
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
tte_set_special(0xD000); // Blue
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.chips);
tte_write(score_buffer);
cursorPosX += EFFECT_TEXT_SEPARATION_AMOUNT;
cursorPosX += joker_score_display_offset_px;
}
if (joker_effect.mult > 0)
{
char score_buffer[12];
char score_buffer[INT_MAX_DIGITS + 2];
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
tte_set_special(0xE000); // Red
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.mult);
tte_write(score_buffer);
cursorPosX += EFFECT_TEXT_SEPARATION_AMOUNT;
cursorPosX += joker_score_display_offset_px;
}
if (joker_effect.xmult > 0)
{
char score_buffer[12];
char score_buffer[INT_MAX_DIGITS + 2];
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
tte_set_special(0xE000); // Red
snprintf(score_buffer, sizeof(score_buffer), "X%d", joker_effect.xmult);
tte_write(score_buffer);
cursorPosX += EFFECT_TEXT_SEPARATION_AMOUNT;
cursorPosX += joker_score_display_offset_px;
}
if (joker_effect.money > 0)
{
char score_buffer[12];
char score_buffer[INT_MAX_DIGITS + 2];
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
tte_set_special(0xC000); // Yellow
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.money);
tte_write(score_buffer);
cursorPosX += EFFECT_TEXT_SEPARATION_AMOUNT;
cursorPosX += joker_score_display_offset_px;
}
joker_object->joker->processed = true; // Mark the joker as processed
+13 -8
View File
@@ -1,3 +1,4 @@
#include "game.h"
#include "joker.h"
#include "util.h"
#include "hand_analysis.h"
@@ -178,7 +179,8 @@ static JokerEffect half_joker_effect(Joker *joker, Card *scored_card) {
if (scored_card != NULL)
return effect; // if card != null, we are not at the end-phase of scoring yet
if (get_played_top() + 1 <= 3) // game.c has hand_get_size() but it's not accesible here
int played_size = get_played_top() + 1;
if (played_size <= 3)
effect.mult = 20;
return effect;
@@ -190,12 +192,13 @@ static JokerEffect joker_stencil_effect(Joker *joker, Card *scored_card) {
return effect; // if card != null, we are not at the end-phase of scoring yet
// +1 xmult per empty joker slot...
int jokers_top = get_jokers_top();
effect.xmult = (MAX_JOKERS_HELD_SIZE-1) - jokers_top;
int num_jokers = get_jokers_top() + 1;
effect.xmult = (MAX_JOKERS_HELD_SIZE) - num_jokers;
// ...and also each stencil_joker adds +1 xmult
JokerObject** jokers = get_jokers();
for (int i = 0; i < jokers_top; i++ )
for (int i = 0; i < num_jokers; i++ )
{
if (jokers[i]->joker->id == JOKER_STENCIL_ID)
effect.xmult++;
@@ -271,8 +274,8 @@ static JokerEffect blackboard_joker_effect(Joker *joker, Card *scored_card) {
bool all_cards_are_spades_or_clubs = true;
CardObject** hand = get_hand_array();
int hand_top = get_hand_top();
for (int i = 0; i < hand_top; i++ )
int hand_size = hand_get_size();
for (int i = 0; i < hand_size; i++ )
{
u8 suit = hand[i]->card->suit;
if (suit == HEARTS || suit == DIAMONDS) {
@@ -470,11 +473,12 @@ const JokerInfo joker_registry[] = {
{ COMMON_JOKER, 4, crafty_joker_effect },
{ COMMON_JOKER, 5, half_joker_effect },
{ UNCOMMON_JOKER, 8, joker_stencil_effect },
{ COMMON_JOKER, 4, misprint_joker_effect },
{ COMMON_JOKER, 5, banner_joker_effect },
{ COMMON_JOKER, 4, walkie_talkie_joker_effect },
{ UNCOMMON_JOKER, 8, fibonnaci_joker_effect },
{ COMMON_JOKER, 5, banner_joker_effect },
{ UNCOMMON_JOKER, 6, blackboard_joker_effect },
{ COMMON_JOKER, 5, mystic_summit_joker_effect },
{ COMMON_JOKER, 4, misprint_joker_effect },
{ UNCOMMON_JOKER, 6, blackboard_joker_effect },
{ COMMON_JOKER, 5, blue_joker_effect },
{ COMMON_JOKER, 5, raised_fist_joker_effect },
@@ -487,6 +491,7 @@ const JokerInfo joker_registry[] = {
{ COMMON_JOKER, 4, smiley_face_joker_effect },
{ COMMON_JOKER, 4, even_steven_joker_effect },
{ COMMON_JOKER, 4, odd_todd_joker_effect },
};
static const size_t joker_registry_size = NUM_ELEM_IN_ARR(joker_registry);
+4 -43
View File
@@ -7,6 +7,7 @@
#include "game.h"
#include "blind.h"
#include "joker.h"
#include "affine_background.h"
#include "graphic_utils.h"
// Graphics
@@ -17,29 +18,6 @@
#include "soundbank.h"
#include "soundbank_bin.h"
#define MAP_AFF_SIZE 0x0100
BG_AFFINE bgaff;
AFF_SRC_EX asx = {32<<8, 64<<8, 120, 80, 0x0100, 0x0100, 0};
void init_map() // Temp function derived from libtonc example
{
// Grit is fucking garbage and doesn't allow you to have a palette offset, they all get loaded at 0x0000. I have to manually change each tile's palette bank
unsigned int correctedTiles[affine_background_gfxTilesLen / 4];
for (int i = 0; i < affine_background_gfxTilesLen / 4; i++)
{
correctedTiles[i] = affine_background_gfxTiles[i] | 0xF0F0F0F0; // I brute forced this. I have no idea how this would and does work, but it does. We'll just leave this here for now.
// Actually for future reference, each number seems to equate to an XY on the palette, so 0xF0F0F0F0 would just push everything down 15 colors, since the second number isn't set.
}
memcpy(&tile8_mem[AFFINE_BG_CBB], correctedTiles, affine_background_gfxTilesLen);
GRIT_CPY(&se_mem[AFFINE_BG_SBB], affine_background_gfxMap);
memcpy16(&pal_bg_mem[16 * 15], affine_background_gfxPal, 5);
bgaff = bg_aff_default;
}
void init()
{
irq_init(NULL);
@@ -66,7 +44,7 @@ void init()
// BG1 is the main background layer
REG_BG1CNT = BG_PRIO(1) | BG_CBB(MAIN_BG_CBB) | BG_SBB(MAIN_BG_SBB) | BG_8BPP;
// BG2 is the affine background layer
REG_BG2CNT = BG_PRIO(2) | BG_CBB(AFFINE_BG_CBB) | BG_SBB(AFFINE_BG_CBB) | BG_8BPP | BG_WRAP | BG_AFF_32x32;
REG_BG2CNT = BG_PRIO(2) | BG_CBB(AFFINE_BG_CBB) | BG_SBB(AFFINE_BG_SBB) | BG_8BPP | BG_WRAP | BG_AFF_32x32;
int win1_left = 72;
int win1_top = 44;
@@ -93,9 +71,8 @@ void init()
REG_DISPCNT = DCNT_MODE1 | DCNT_OBJ_1D | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_OBJ | DCNT_WIN0 | DCNT_WIN1;
init_map(); // Initialize the map for the background
// Initialize subsystems
affine_background_init();
sprite_init();
card_init();
blind_init();
@@ -103,24 +80,9 @@ void init()
game_init();
}
void background_update() // This needs to be called before update() and draw() because setting REG_BG_AFFINE during an HBlank can cause issues
{
static uint timer = 0;
timer++;
// These values are not permament, just the current configuration for the affine background
asx.tex_x += 5;
asx.tex_y += 12;
asx.sx = (lu_sin(timer * 100)) >> 8; // Scale the sine value to fit in a s16
asx.sx += 256; // Add 256 to the sine value to make it positive
asx.sy = (lu_sin(timer * 100 + 0x4000)) >> 8; // Scale the sine value to fit in a s16
asx.sy += 256; // Add 256 to the sine value to make it positive
bg_rotscale_ex(&bgaff, &asx);
REG_BG_AFFINE[2]= bgaff;
}
void update()
{
affine_background_update();
game_update();
}
@@ -138,7 +100,6 @@ int main()
VBlankIntrWait();
mmFrame();
key_poll();
background_update();
update();
draw();
}
+17 -8
View File
@@ -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()
{
@@ -135,9 +145,9 @@ void sprite_object_reset_transform(SpriteObject* sprite_object)
sprite_object->y = 0;
sprite_object->vx = 0;
sprite_object->vy = 0;
sprite_object->tscale = float2fx(1.0f); // Target scale
sprite_object->scale = float2fx(1.0f);
sprite_object->vscale = float2fx(0.0f);
sprite_object->tscale = FIX_ONE; // Target scale
sprite_object->scale = FIX_ONE;
sprite_object->vscale = 0;
sprite_object->trotation = 0; // Target rotation
sprite_object->rotation = 0;
sprite_object->vrotation = 0;
@@ -153,7 +163,7 @@ void sprite_object_update(SpriteObject* sprite_object)
sprite_object->vrotation += (sprite_object->trotation - sprite_object->rotation) / 8; // Rotate the card when it's played
// set velocity to 0 if it's close enough to the target
const float epsilon = float2fx(0.01f);
const FIXED epsilon = float2fx(0.01f);
if (sprite_object->vx < epsilon && sprite_object->vx > -epsilon && sprite_object->vy < epsilon && sprite_object->vy > -epsilon)
{
sprite_object->vx = 0;
@@ -201,13 +211,12 @@ void sprite_object_update(SpriteObject* sprite_object)
void sprite_object_shake(SpriteObject* sprite_object, mm_word sound_id)
{
sprite_object->vscale = float2fx(0.3f); // Scale down the card when it's scored
sprite_object->vrotation = float2fx(8.0f); // Rotate the card when it's scored
sprite_object->vscale = float2fx(0.3f);
sprite_object->vrotation = float2fx(8.0f); //Rotate the card when it's scored
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)