GBAlatro
A Demake of Balatro for the GBA
Loading...
Searching...
No Matches
sprite.c
1#include "sprite.h"
2
3#include "audio_utils.h"
4#include "game.h"
5#include "game_variables.h"
6#include "graphic_utils.h"
7#include "item.h"
8#include "mgba_logger.h"
9#include "pool.h"
10#include "random.h"
11#include "soundbank.h"
12#include "util.h"
13
14#include <maxmod.h>
15#include <stdlib.h>
16#include <tonc.h>
17#include <tonc_oam.h>
18
19// Damping Constants: SPRING_DAMP_NUMERATOR/2^SPRING_DAMP_DENOM_SHIFT ~ 0.699 damping factor
20//
21// SPRING_DAMP_ROUNDING = 2^(SPRING_DAMP_DENOM_SHIFT - 1) rounding for fixed-point arithmetic by
22// adding half the denominator to round instead of truncating
23#define SPRING_DAMP_NUMERATOR 179
24#define SPRING_DAMP_DENOM_SHIFT 8
25#define SPRING_DAMP_ROUNDING (1 << (SPRING_DAMP_DENOM_SHIFT - 1))
26
27OBJ_ATTR obj_buffer[MAX_SPRITES];
28OBJ_AFFINE* obj_aff_buffer = (OBJ_AFFINE*)obj_buffer;
29
30static Sprite* free_sprites[MAX_SPRITES] = {NULL};
31static bool free_affines[MAX_AFFINES] = {false};
32
33static List sprite_objects_list = LIST_DEFAULT;
34
35// Sprite methods
36Sprite* sprite_new(u16 a0, u16 a1, u32 tid, u32 pb, s16 sprite_index)
37{
38 Sprite* sprite = POOL_GET(Sprite);
39
40 sprite->obj = NULL;
41 sprite->aff = NULL;
42
43 if (!free_sprites[sprite_index])
44 {
45 free_sprites[sprite_index] = sprite;
46 }
47 else
48 {
49 POOL_FREE(Sprite, sprite);
50 return NULL;
51 }
52
53 if (a0 & ATTR0_AFF)
54 {
55 int aff_index = MAX_AFFINES;
56
57 for (int i = 0; i < MAX_AFFINES; i++)
58 {
59 if (!free_affines[i])
60 {
61 free_affines[i] = true;
62 aff_index = i;
63 break;
64 }
65 }
66
67 if (aff_index == MAX_AFFINES)
68 {
69 POOL_FREE(Sprite, sprite);
70 return NULL;
71 }
72
73 a1 = a1 | ATTR1_AFF_ID(aff_index);
74
75 sprite->obj = &obj_buffer[sprite_index];
76 sprite->aff = &obj_aff_buffer[aff_index];
77 obj_set_attr(sprite->obj, a0, a1, ATTR2_PALBANK(pb) | tid);
78 obj_aff_identity(&obj_aff_buffer[aff_index]);
79 }
80 else
81 {
82 sprite->obj = &obj_buffer[sprite_index];
83 obj_set_attr(sprite->obj, a0, a1, ATTR2_PALBANK(pb) | tid);
84 }
85
86 sprite->idx = sprite_index;
87
88 sprite->mode = a0 & ATTR0_MODE_MASK;
89
90 return sprite;
91}
92
93void sprite_destroy(Sprite** sprite)
94{
95 if (*sprite == NULL)
96 return;
97
98 obj_hide((*sprite)->obj);
99
100 if ((*sprite)->aff != NULL)
101 {
102 free_affines[(*sprite)->aff - obj_aff_buffer] = false;
103 }
104
105 free_sprites[(*sprite)->idx] = NULL;
106
107 POOL_FREE(Sprite, *sprite);
108
109 *sprite = NULL;
110}
111
112/* The following functions don't check sprite->obj, assuming it shouldn't be NULL
113 * if sprite != NULL since it's set in the constructor
114 */
115
117{
118 GBAL_RETURN_IF_NULL_RET(sprite, UNDEFINED);
119
120 return (s16)(sprite->obj - obj_buffer);
121}
122
123bool sprite_get_width(Sprite* sprite, int* width)
124{
125 GBAL_RETURN_IF_NULL_RET(sprite, false);
126 GBAL_RETURN_IF_NULL_RET(width, false);
127
128 *width = obj_get_width(sprite->obj);
129 return true;
130}
131
132bool sprite_get_height(Sprite* sprite, int* height)
133{
134 GBAL_RETURN_IF_NULL_RET(sprite, false);
135 GBAL_RETURN_IF_NULL_RET(height, false);
136
137 *height = obj_get_height(sprite->obj);
138 return true;
139}
140
141bool sprite_get_dimensions(Sprite* sprite, int* width, int* height)
142{
143 GBAL_RETURN_IF_NULL_RET(sprite, false);
144 GBAL_RETURN_IF_NULL_RET(width, false);
145 GBAL_RETURN_IF_NULL_RET(height, false);
146
147 const u8* size = obj_get_size(sprite->obj);
148 *width = size[0];
149 *height = size[1];
150 return true;
151}
152
153// Sprite functions
155{
156 oam_init(obj_buffer, MAX_SPRITES);
157}
158
160{
161 oam_copy(oam_mem, obj_buffer, MAX_SPRITES);
162}
163
164int sprite_get_pb(const Sprite* sprite)
165{
166 GBAL_RETURN_IF_NULL_RET(sprite, UNDEFINED);
167
168 return (sprite->obj->attr2 & ATTR2_PALBANK_MASK) >> ATTR2_PALBANK_SHIFT;
169}
170
171void sprite_hide(Sprite* sprite)
172{
174
175 obj_hide(sprite->obj);
176}
177
179{
181
182 obj_unhide(sprite->obj, sprite->mode);
183}
184
185// SpriteObject methods
187{
188 GBAL_RETURN_IF_NULL_VOID(sprite_object);
189
190 sprite_object->sprite = NULL;
191 sprite_object_reset_transform(sprite_object);
192 sprite_object->focused = false;
193
194 list_push_back(&sprite_objects_list, sprite_object);
195}
196
198{
199 GBAL_RETURN_IF_NULL_VOID(sprite_object);
200
201 list_remove_data(&sprite_objects_list, sprite_object);
202 sprite_destroy(&sprite_object->sprite);
203}
204
205void sprite_object_set_sprite(SpriteObject* sprite_object, Sprite* sprite)
206{
207 GBAL_RETURN_IF_NULL_VOID(sprite_object);
208
209 sprite_destroy(&sprite_object->sprite); // Destroy the old sprite if it exists
210 sprite_object->sprite = sprite;
211}
212
214{
215 GBAL_RETURN_IF_NULL_VOID(sprite_object);
216
217 sprite_hide(sprite_object->sprite);
218}
219
221{
222 GBAL_RETURN_IF_NULL_VOID(sprite_object);
223
224 sprite_unhide(sprite_object->sprite);
225}
226
228{
229 GBAL_RETURN_IF_NULL_VOID(sprite_object);
230
231 sprite_object_position(sprite_object, 0, 0); // Target position
232 sprite_object->vx = 0;
233 sprite_object->vy = 0;
234 sprite_object->tscale = FIX_ONE; // Target scale
235 sprite_object->scale = FIX_ONE;
236 sprite_object->vscale = 0;
237 sprite_object->trotation = 0; // Target rotation
238 sprite_object->rotation = 0;
239 sprite_object->vrotation = 0;
240}
241
242/* The following functions are in the SpriteObject update loop which is called each frame
243 * so they avoid argument NULL-checks for efficiency.
244 */
245
246static inline bool sprite_object_has_velocity(const SpriteObject* sprite_object)
247{
248 return sprite_object->vx != 0 || sprite_object->vy != 0 || sprite_object->vscale != 0 ||
249 sprite_object->vrotation != 0;
250}
251
252static inline bool sprite_object_at_target(const SpriteObject* s)
253{
254 return s->x == s->tx && s->y == s->ty && s->scale == s->tscale && s->rotation == s->trotation;
255}
256
257static inline bool is_sprite_object_static(const SpriteObject* sprite_object)
258{
259 return !sprite_object_has_velocity(sprite_object) && sprite_object_at_target(sprite_object);
260}
261
262static inline IWRAM_CODE void update_sprite_position(SpriteObject* sprite_object)
263{
264 sprite_object->vx += ((sprite_object->tx - sprite_object->x) * g_game_vars.game_speed) / 8;
265 sprite_object->vy += ((sprite_object->ty - sprite_object->y) * g_game_vars.game_speed) / 8;
266
267 // Scale up the card when it's played
268 sprite_object->vscale += (sprite_object->tscale - sprite_object->scale) / 8;
269
270 // Rotate the card when it's played
271 sprite_object->vrotation += (sprite_object->trotation - sprite_object->rotation) / 8;
272
273 const FIXED epsilon = (FIX_ONE >> 6); // = 1/2^6 = 0.015625
274
275 // Snap to target position when velocity is negligible to avoid infinite approach
276 if (abs(sprite_object->vx) < epsilon && abs(sprite_object->vy) < epsilon)
277 {
278 sprite_object->vx = 0;
279 sprite_object->vy = 0;
280
281 sprite_object->x = sprite_object->tx;
282 sprite_object->y = sprite_object->ty;
283 }
284 else
285 {
286 sprite_object->vx = (sprite_object->vx * SPRING_DAMP_NUMERATOR + SPRING_DAMP_ROUNDING) >>
287 SPRING_DAMP_DENOM_SHIFT;
288 sprite_object->vy = (sprite_object->vy * SPRING_DAMP_NUMERATOR + SPRING_DAMP_ROUNDING) >>
289 SPRING_DAMP_DENOM_SHIFT;
290
291 sprite_object->x += sprite_object->vx;
292 sprite_object->y += sprite_object->vy;
293 }
294
295 // Set scale to 0 if it's close enough to the target
296 if (abs(sprite_object->vscale) < epsilon)
297 {
298 sprite_object->vscale = 0;
299 sprite_object->scale = sprite_object->tscale;
300 }
301 else
302 {
303 sprite_object->vscale =
304 (sprite_object->vscale * SPRING_DAMP_NUMERATOR + SPRING_DAMP_ROUNDING) >>
305 SPRING_DAMP_DENOM_SHIFT;
306 sprite_object->scale += sprite_object->vscale;
307 }
308
309 // For rotation, prioritize snapping to target if close enough, then zero velocity.
310 if (abs(sprite_object->vrotation) < epsilon)
311 {
312 sprite_object->vrotation = 0;
313 sprite_object->rotation = sprite_object->trotation;
314 }
315 else // Apply damping and update rotation if not yet settled
316 {
317 sprite_object->vrotation =
318 (sprite_object->vrotation * SPRING_DAMP_NUMERATOR + SPRING_DAMP_ROUNDING) >>
319 SPRING_DAMP_DENOM_SHIFT;
320 sprite_object->rotation += sprite_object->vrotation;
321 }
322
323 // Apply rotation and scale to the sprite
324 obj_aff_rotscale(
325 sprite_object->sprite->aff,
326 sprite_object->scale,
327 sprite_object->scale,
328 -sprite_object->vx + sprite_object->rotation
329 );
330}
331
332IWRAM_CODE void sprite_object_update(SpriteObject* sprite_object)
333{
334 if (!is_sprite_object_static(sprite_object))
335 update_sprite_position(sprite_object);
336
337 sprite_position(sprite_object->sprite, fx2int(sprite_object->x), fx2int(sprite_object->y));
338}
339
341{
342 SpriteObject* sprite_object = NULL;
343 ListItr itr = list_itr_create(&sprite_objects_list);
344 while ((sprite_object = list_itr_next(&itr)))
345 {
346 sprite_object_update(sprite_object);
347 }
348}
349
350void sprite_object_shake(SpriteObject* sprite_object, mm_word sound_id)
351{
352 GBAL_RETURN_IF_NULL_VOID(sprite_object);
353
354 sprite_object->vscale = float2fx(0.3f);
355 sprite_object->vrotation = float2fx(8.0f); // Rotate the card when it's scored
356
357 if (sound_id == UNDEFINED)
358 return; // If no sound ID is provided, do nothing
359
361}
362
364{
365 GBAL_RETURN_IF_NULL_RET(sprite_object, NULL);
366
367 return sprite_object->sprite;
368}
369
370void sprite_object_set_focus(SpriteObject* sprite_object, bool focus)
371{
372 GBAL_RETURN_IF_NULL_VOID(sprite_object);
373
374 if (sprite_object->focused == focus)
375 {
376 return;
377 }
378 sprite_object->focused = focus;
379
380 play_sfx(
381 SFX_CARD_FOCUS,
382 MM_BASE_PITCH_RATE + rng_get_u32(RNG_SEQ_MISC) % CARD_FOCUS_SFX_PITCH_OFFSET_RANGE,
384 );
385 sprite_object->ty = sprite_object->ty + int2fx((focus ? -1 : 1) * SPRITE_FOCUS_RAISE_PX);
386}
387
388bool sprite_object_get_width(SpriteObject* sprite_object, int* width)
389{
390 GBAL_RETURN_IF_NULL_RET(sprite_object, false);
391
392 return sprite_get_width(sprite_object->sprite, width);
393}
394
395bool sprite_object_get_height(SpriteObject* sprite_object, int* height)
396{
397 GBAL_RETURN_IF_NULL_RET(sprite_object, false);
398
399 return sprite_get_height(sprite_object->sprite, height);
400}
401
402bool sprite_object_get_dimensions(SpriteObject* sprite_object, int* width, int* height)
403{
404 GBAL_RETURN_IF_NULL_RET(sprite_object, false);
405
406 return sprite_get_dimensions(sprite_object->sprite, width, height);
407}
408
410{
411 GBAL_RETURN_IF_NULL_RET(sprite_object, false);
412 return sprite_object->focused;
413}
414
415static Rect sprite_object_get_text_rect_under(SpriteObject* sprite_object)
416{
417 int height = 0;
418 int width = 0;
419 Rect ret_rect = {0};
420
421 GBAL_RETURN_IF_NULL_RET(sprite_object, ret_rect);
422
423 if (sprite_object_get_dimensions(sprite_object, &width, &height) == false)
424 {
425 // fallback
426 height = CARD_SPRITE_SIZE;
427 width = CARD_SPRITE_SIZE;
428 }
429
430 ret_rect.left = fx2int(sprite_object->tx);
431 ret_rect.top = fx2int(sprite_object->ty) + height + TILE_SIZE;
432 ret_rect.right = ret_rect.left + width;
433 ret_rect.bottom = ret_rect.top + TTE_CHAR_SIZE;
434
435 return ret_rect;
436}
437
438void sprite_object_print_text_under(SpriteObject* sprite_object, const char text[])
439{
440 GBAL_RETURN_IF_NULL_VOID(sprite_object);
441
442 Rect text_rect = sprite_object_get_text_rect_under(sprite_object);
443 update_text_rect_to_center_str(&text_rect, text, SCREEN_LEFT);
444 tte_printf("#{P:%d,%d; cx:0x%X000}%s", text_rect.left, text_rect.top, TTE_YELLOW_PB, text);
445}
446
447void sprite_object_print_price_under(SpriteObject* sprite_object, int price)
448{
449 GBAL_RETURN_IF_NULL_VOID(sprite_object);
450
451 // + 2 for null-terminator and "$"
452 char price_str_buff[INT_MAX_DIGITS + 2];
453 snprintf(price_str_buff, sizeof(price_str_buff), "$%d", price);
454 sprite_object_print_text_under(sprite_object, price_str_buff);
455}
456
458{
459 GBAL_RETURN_IF_NULL_VOID(sprite_object);
460
461 Rect text_rect = sprite_object_get_text_rect_under(sprite_object);
462
463 // Add SPRITE_FOCUS_RAISE_PX to cover the focused case
464 text_rect.bottom = text_rect.bottom + SPRITE_FOCUS_RAISE_PX;
465
466 tte_erase_rect_wrapper(text_rect);
467}
Utilities for using maxmod to play sound effects.
#define MM_BASE_PITCH_RATE
The default pitch rate for the sound effect played.
Definition audio_utils.h:37
void play_sfx(mm_word id, mm_word rate, mm_byte volume)
Play a sound effect, wrapper for mmEffectEx() See https://maxmod.org/ref/functions/mm_sound_effect....
#define SFX_DEFAULT_VOLUME
Default volume for sound effects.
Definition audio_utils.h:43
Game global game variables struct definition.
Graphic utility functions.
void update_text_rect_to_center_str(Rect *rect, const char *str, enum ScreenHorzDir bias_direction)
Updates a rect so a string is centered within it.
#define TILE_SIZE
Tile size in pixels, both height and width as tiles are square.
#define TTE_CHAR_SIZE
By default TTE characters occupy a single tile.
void tte_erase_rect_wrapper(Rect rect)
A wrapper for tte_erase_rect that would use the rect struct.
The core structure for items in the shop and inventory. Provides a common API for the shop and invent...
void * list_itr_next(ListItr *itr)
Definition list.c:281
bool list_remove_data(List *list, void *data)
Definition list.c:316
#define LIST_DEFAULT
Default list declaration for empty lists.
Definition list.h:30
void list_push_back(List *list, void *data)
Definition list.c:89
ListItr list_itr_create(List *list)
Definition list.c:257
Interface to interact with the mgba logger.
Common functions to handle RNG manipulation. Using this interface has three goals:
u32 rng_get_u32(enum RngSequence key)
Get the next "randomly" generated number in the sequence corresponding to the given type.
Definition random.c:56
Sprite system for Gbalatro.
bool sprite_object_get_dimensions(SpriteObject *sprite_object, int *width, int *height)
Get the width and height of SpriteObject's registered Sprite.
Definition sprite.c:402
bool sprite_object_is_focused(SpriteObject *sprite_object)
Get the focused variable from a SpriteObject.
Definition sprite.c:409
bool sprite_get_width(Sprite *sprite, int *width)
Get a Sprite's width.
Definition sprite.c:123
INLINE void sprite_position(Sprite *sprite, int x, int y)
Set sprite position. Inlined for efficiency.
Definition sprite.h:400
void sprite_object_unhide(SpriteObject *sprite_object)
Unhides the SpriteObject by manipulating ATTR0_HIDE in OAM. The sprite's ATTR0_MODE is maintained fro...
Definition sprite.c:220
void sprite_object_set_focus(SpriteObject *sprite_object, bool focus)
Set the focus for SpriteObject Raises the object by SPRITE_FOCUS_RAISE_PX.
Definition sprite.c:370
void sprite_object_init(SpriteObject *sprite_object)
Initialize a SpriteObject to a default state. Must be called only once per SpriteObject when it is cr...
Definition sprite.c:186
void sprite_object_destroy(SpriteObject *sprite_object)
Destroy SpriteObject.
Definition sprite.c:197
void sprite_object_print_price_under(SpriteObject *sprite_object, int price)
Print the price string directly beneath a SpriteObject. More specialized version of sprite_object_pri...
Definition sprite.c:447
s16 sprite_get_layer(Sprite *sprite)
Get index of Sprite in the GBA object buffer.
Definition sprite.c:116
void sprite_object_print_text_under(SpriteObject *sprite_object, const char text[])
Print the given string directly beneath a SpriteObject. This is used only for Cards for now.
Definition sprite.c:438
void sprite_init(void)
Initialize GBAlatro sprite system.
Definition sprite.c:154
void sprite_object_reset_transform(SpriteObject *sprite_object)
Reset SpriteObject's transform back to default values.
Definition sprite.c:227
int sprite_get_pb(const Sprite *sprite)
Get the palette bank of a Sprite.
Definition sprite.c:164
bool sprite_object_get_width(SpriteObject *sprite_object, int *width)
Get a SpriteObject's width.
Definition sprite.c:388
void sprite_object_set_sprite(SpriteObject *sprite_object, Sprite *sprite)
Register a Sprite to an associated SpriteObject.
Definition sprite.c:205
void sprite_object_update_all(void)
Update all SpriteObjects, to be called once per frame in the main update loop.
Definition sprite.c:340
bool sprite_get_dimensions(Sprite *sprite, int *width, int *height)
Get a Sprite's width and height.
Definition sprite.c:141
bool sprite_object_get_height(SpriteObject *sprite_object, int *height)
Get a SpriteObject's height.
Definition sprite.c:395
INLINE void sprite_object_position(SpriteObject *sprite_object, int x, int y)
Set sprite_object position. Inlined for efficiency.
Definition sprite.h:417
void sprite_object_shake(SpriteObject *sprite_object, mm_word sound_id)
Shake SpriteObject on screen and play a sound.
Definition sprite.c:350
Sprite * sprite_new(u16 a0, u16 a1, u32 tid, u32 pb, s16 sprite_index)
Allocate and retrieve a pointer to a valid Sprite.
Definition sprite.c:36
void sprite_object_erase_text_under(SpriteObject *sprite_object)
Erase the text within the Rect directly beneath a SpriteObject. This is used only for Cards for now.
Definition sprite.c:457
bool sprite_get_height(Sprite *sprite, int *height)
Get a Sprites's height.
Definition sprite.c:132
void sprite_destroy(Sprite **sprite)
Destroy Sprite.
Definition sprite.c:93
Sprite * sprite_object_get_sprite(SpriteObject *sprite_object)
Get a SpriteObject's registered Sprite.
Definition sprite.c:363
void sprite_hide(Sprite *sprite)
Hides the sprite by manipulating ATTR0_HIDE in OAM.
Definition sprite.c:171
void sprite_unhide(Sprite *sprite)
Unhides the sprite by manipulating ATTR0_HIDE in OAM. The sprite's ATTR0_MODE is maintained from the ...
Definition sprite.c:178
void sprite_draw(void)
Draw Sprites to screen, to be called once per frame.
Definition sprite.c:159
IWRAM_CODE void sprite_object_update(SpriteObject *sprite_object)
Update a SpriteObject, to be called once per frame per active SpriteObject.
Definition sprite.c:332
void sprite_object_hide(SpriteObject *sprite_object)
Hides the SpriteObject by manipulating ATTR0_HIDE in OAM.
Definition sprite.c:213
An iterator into a list.
Definition list.h:91
A doubly-linked list.
Definition list.h:61
A sprite object is a sprite that is focusable and movable in animation.
Definition sprite.h:61
FIXED x
Current position.
Definition sprite.h:75
bool focused
Focused status (card specific, raise and lower card)
Definition sprite.h:115
FIXED vscale
Scale velocity AKA the rate of change of scaling ops.
Definition sprite.h:95
FIXED rotation
Actual rotation, in units for tonc's obj_aff_rotscale
Definition sprite.h:105
FIXED tx
Target position.
Definition sprite.h:70
FIXED trotation
Target rotation.
Definition sprite.h:100
FIXED vx
Velocity.
Definition sprite.h:80
FIXED scale
Current Scale, in units for tonc's obj_aff_rotscale
Definition sprite.h:90
Sprite * sprite
Sprite configuration info.
Definition sprite.h:65
FIXED tscale
Target Scale.
Definition sprite.h:85
FIXED vrotation
Rotation velocity.
Definition sprite.h:110
Sprite struct for GBA hardware specifics.
Definition sprite.h:29
OBJ_AFFINE * aff
GBA sprite affine matrices registers info.
Definition sprite.h:38
s16 idx
Sprite index in memory managed by GBAlatro.
Definition sprite.h:48
u16 mode
The mode of the sprite (regular, affine, etc.), set when the sprite is created corresponds to A0 & AT...
Definition sprite.h:54
OBJ_ATTR * obj
GBA sprite attribute registers info (A0-A2)
Definition sprite.h:33
Utilities relating around number string representation and protected arithmatic helper functions.
#define GBAL_RETURN_IF_NULL_RET(param, ret_val)
Checks if param is equal to NULL and prints error message and returns in case it is....
Definition util.h:95
#define GBAL_RETURN_IF_NULL_VOID(param)
Checks if param is NULL and prints error message and returns in case it is. Useful for checking argum...
Definition util.h:76