GBAlatro
A Demake of Balatro for the GBA
Loading...
Searching...
No Matches
skip_tag.c
Go to the documentation of this file.
1
5#include "skip_tag.h"
6
7#include "audio_utils.h"
8#include "game/blind_select.h"
9#include "game_variables.h"
10#include "joker.h"
11#include "mgba_logger.h"
12#include "pool.h"
13#include "skip_tags_gfx.h"
14#include "soundbank.h"
15#include "state_machine.h"
16#include "timer.h"
17#include "util.h"
18
19#include <stdlib.h> //#include "random.h"
20#include <tonc.h>
21
22#define MAX_NON_OVERLAPING_TAG_SPRITES 5
23#define SKIP_TAG_HIDE_X_OFFSET 20
24
25#define SKIP_TAG_GET_BOUNCE_STRENGTH (float2fx(-0.3f))
26#define SKIP_TAG_TRIGGER_BOUNCE_STRENGTH (float2fx(-0.4f))
27
28// clang-format off
29// Points x y
30static const BG_POINT OWNED_SKIP_TAGS_BASE_POS = {219, 97};
31// Sizes
32static const int OWNED_SKIP_TAGS_STACK_HEIGHT = 64;
33static const int OWNED_SKIP_TAGS_SPACING = 12;
34// clang-format on
35
40#define NB_ANTE1_SKIP_TAGS 7
41
46#define NB_SKIP_TAG_TYPES 10
47
48// Not all Skip Tags can be drawn on Ante 1, so we need 2 different roll tables
49// one for Ante 1 only, and one for all other Antes.
50static const u8 ante1_skip_tags_roll_table[NB_ANTE1_SKIP_TAGS] = {
51 SKIP_TAG_TYPE_INVESTMENT,
52 SKIP_TAG_TYPE_BOSS,
53 SKIP_TAG_TYPE_DOUBLE,
54 SKIP_TAG_TYPE_JUGGLE,
55 SKIP_TAG_TYPE_D6,
56 SKIP_TAG_TYPE_SPEED,
57 SKIP_TAG_TYPE_ECONOMY
58};
59
60// Won't be needed if all tags are implemented, but likely won't ever be the case
61static const u8 all_skip_tags_roll_table[NB_SKIP_TAG_TYPES] = {
62 SKIP_TAG_TYPE_INVESTMENT,
63 SKIP_TAG_TYPE_BOSS,
64 SKIP_TAG_TYPE_HANDY,
65 SKIP_TAG_TYPE_GARBAGE,
66 SKIP_TAG_TYPE_DOUBLE,
67 SKIP_TAG_TYPE_JUGGLE,
68 SKIP_TAG_TYPE_D6,
69 SKIP_TAG_TYPE_TOP_UP,
70 SKIP_TAG_TYPE_SPEED,
71 SKIP_TAG_TYPE_ECONOMY
72};
73
74static List s_owned_skip_tags;
75
77{
78 return &s_owned_skip_tags;
79}
80
81// SkipTag new/destroy functions
82
84{
85 SkipTag* tag = POOL_GET(SkipTag);
86 GBAL_RETURN_IF_NULL(tag, NULL);
87
88 tag->type = tag_type;
90
91 return tag;
92}
93
94void skip_tag_set_sprite(SkipTag* tag, s16 layer)
95{
97
98 // Set tags palette the first time we ask for a sprite
99 static bool pb_init = false;
100 if (!pb_init)
101 {
102 GRIT_CPY(&pal_obj_mem[PAL_ROW_LEN * SKIP_TAGS_PB], skip_tags_gfxPal);
103 pb_init = true;
104 }
105
106 int tile_index = sprite_get_tid(SKIP_TAG_SPRITE, layer);
107 memcpy32(
108 &tile_mem[TILE_MEM_OBJ_CHARBLOCK0_IDX][tile_index],
109 &skip_tags_gfxTiles[tag->type * SKIP_TAG_SPRITE_TILES * TILE_SIZE],
110 SKIP_TAG_SPRITE_TILES * TILE_SIZE
111 );
112
113 // Recreate sprite
114 if (tag->sprite)
115 {
116 sprite_destroy(&tag->sprite);
117 }
118 Sprite* sprite = sprite_new(
119 ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF,
120 ATTR1_SIZE_16,
121 tile_index,
122 SKIP_TAGS_PB,
123 sprite_get_starting_layer(SKIP_TAG_SPRITE) + layer
124 );
126}
127
129{
130 // Nothing to do
131 if (tag == NULL || *tag == NULL)
132 return;
133
135 POOL_FREE(SkipTag, *tag);
136 *tag = NULL;
137}
138
139// Misc functions
140
141void set_owned_skip_tags_moved_offscreen(bool move_offscreen)
142{
143 SkipTag* tag;
144 ListItr tag_itr = list_itr_create(&s_owned_skip_tags);
145 int tag_xpos =
146 int2fx(OWNED_SKIP_TAGS_BASE_POS.x + (move_offscreen ? SKIP_TAG_HIDE_X_OFFSET : 0));
147
148 while ((tag = list_itr_next(&tag_itr)))
149 {
150 tag->tx = tag_xpos;
151 }
152}
153
155{
156 enum SkipTagTypes tag_type = 0;
157 if (g_game_vars.ante == 1)
158 {
159 tag_type = ante1_skip_tags_roll_table[rng_get_u32(RNG_SEQ_SKIP_TAGS) % NB_ANTE1_SKIP_TAGS];
160 }
161 else
162 {
163 tag_type = all_skip_tags_roll_table[rng_get_u32(RNG_SEQ_SKIP_TAGS) % NB_SKIP_TAG_TYPES];
164 }
165
166 return skip_tag_new(tag_type);
167}
168
169static inline int get_skip_tag_sprites_spacing(void)
170{
171 int nb_owned_tags = list_get_len(&s_owned_skip_tags);
172 return (nb_owned_tags <= MAX_NON_OVERLAPING_TAG_SPRITES)
173 ? OWNED_SKIP_TAGS_SPACING
174 : OWNED_SKIP_TAGS_STACK_HEIGHT / nb_owned_tags;
175}
176
177static void rearrange_skip_tag_sprites(int nb_owned_tags, int tag_spacing)
178{
179 for (int idx = 0; idx < nb_owned_tags; idx++)
180 {
181 SkipTag* tmp_tag = list_get_at_idx(&s_owned_skip_tags, idx);
182
183 if (tmp_tag == NULL)
184 continue;
185
186 BG_POINT dest_pos = {
187 OWNED_SKIP_TAGS_BASE_POS.x,
188 OWNED_SKIP_TAGS_BASE_POS.y - idx * tag_spacing
189 };
190
191 sprite_object_set_target((SpriteObject*)tmp_tag, dest_pos);
192 }
193}
194
195// Unused for now, may be useful for upcoming tags
198{
199 SkipTag* tag;
200 ListItr tag_itr = list_itr_create(&s_owned_skip_tags);
201
202 while ((tag = list_itr_next(&tag_itr)))
203 {
204 if (tag->type == tag_type)
205 {
206 return true;
207 }
208 }
209
210 return false;
211}
212
213int skip_tag_count(enum SkipTagTypes tag_type)
214{
215 int count = 0;
216 SkipTag* tag;
217 ListItr tag_itr = list_itr_create(&s_owned_skip_tags);
218
219 while ((tag = list_itr_next(&tag_itr)))
220 {
221 if (tag->type == tag_type)
222 {
223 count++;
224 }
225 }
226
227 return count;
228}
229
230void add_skip_tag(SkipTag** blind_tag)
231{
232 GBAL_RETURN_IF_NULL(blind_tag, RET_NONE);
233 GBAL_RETURN_IF_NULL(*blind_tag, RET_NONE);
234
235 // Add to the back, so that the oldest (at the bottom) has the lowest sprite
236 // index and is thus shown on top of the others.
237 // Limit number to MAX_SKIP_TAGS - NB_SKIPPABLE_BLINDS so we always have room in the memory
238 // pool to allocate the two tags in the Blind Select menu. Don't do anything, you just won't
239 // get the tag.
240 int nb_owned_tags = list_get_len(&s_owned_skip_tags);
241 if (nb_owned_tags >= MAX_SKIP_TAGS - NB_SKIPPABLE_BLINDS)
242 {
243 MGBA_FUNC_WARN("Memory pool limit reached, cannot obtain more Skip Tags");
244 return;
245 }
246
247 list_push_back(&s_owned_skip_tags, *blind_tag);
248 nb_owned_tags++;
249
250 BG_POINT new_tag_pos = OWNED_SKIP_TAGS_BASE_POS;
251
252 // If all the tags fix on the screen without issue,
253 // just move the new tag to the top of the stack and that's it
254 if (nb_owned_tags <= MAX_NON_OVERLAPING_TAG_SPRITES)
255 {
256 new_tag_pos.y -= (nb_owned_tags - 1) * OWNED_SKIP_TAGS_SPACING;
257 }
258 // If it's going to overlap the consumables' frame, then we must move all
259 // existing tags down a bit so we can put the new one at the very top
260 else
261 {
262 int even_spacing = get_skip_tag_sprites_spacing();
263
264 // Exclude the new one from the loop here, as it will be snapped into place directly
265 rearrange_skip_tag_sprites(nb_owned_tags - 1, even_spacing);
266 new_tag_pos.y -= even_spacing * (nb_owned_tags - 1);
267 }
268
269 // Create new sprite at the old position to avoid flickering
270 skip_tag_set_sprite(*blind_tag, OWNED_SKIP_TAG_STARTING_LAYER + nb_owned_tags - 1);
271 sprite_object_position((SpriteObject*)(*blind_tag), new_tag_pos.x, new_tag_pos.y);
272 sprite_object_bounce((SpriteObject*)(*blind_tag), SKIP_TAG_GET_BOUNCE_STRENGTH);
273 sprite_object_sway((SpriteObject*)(*blind_tag));
275
276 *blind_tag = NULL;
277}
278
279void remove_skip_tag(int tag_idx)
280{
281 // Remove the tag itself
282 SkipTag* tag = list_get_at_idx(&s_owned_skip_tags, tag_idx);
283 (void)list_remove_at_idx(&s_owned_skip_tags, tag_idx);
284 skip_tag_destroy(&tag);
285
286 rearrange_skip_tag_sprites(list_get_len(&s_owned_skip_tags), get_skip_tag_sprites_spacing());
287}
288
290{
291 ListItr itr = rev_list_itr_create(&s_owned_skip_tags);
292 SkipTag* tag = NULL;
293
294 while ((tag = list_itr_next(&itr)))
295 {
296 skip_tag_destroy(&tag);
297 }
298
299 list_clear(&s_owned_skip_tags);
300}
301
302enum SkipTagProcessState
303{
304 SKIP_TAG_PROCESS_STATE_SEARCH,
305 SKIP_TAG_PROCESS_STATE_TRIGGER,
306 SKIP_TAG_PROCESS_STATE_REMOVE,
307 SKIP_TAG_PROCESS_STATE_MAX
308};
309
310static void skip_tag_search_for_event(void);
311static void skip_tag_trigger_for_event(void);
312static void skip_tag_remove_for_event(void);
313
314static StateInfo tag_process_state_fn[SKIP_TAG_PROCESS_STATE_MAX] = {
315 STATE_INFO_UPDATE_FN_ONLY(skip_tag_search_for_event),
316 STATE_INFO_UPDATE_FN_ONLY(skip_tag_trigger_for_event),
317 STATE_INFO_UPDATE_FN_ONLY(skip_tag_remove_for_event)
318};
319
320static StateMachine tag_process_sm =
321 STATE_MACHINE_DEFINE(tag_process_state_fn, SKIP_TAG_PROCESS_STATE_MAX);
322
323typedef struct
324{
325 // General info about the processing of tags
326 s32 tags_timer;
327 enum SkipTagProcessState tag_process_state;
328 enum SkipTagEvent tag_event;
329 enum SkipTagProcStage tag_proc_stage;
331
332static TagProcessInfo s_tag_process_info = {
333 .tags_timer = TM_ZERO,
334 .tag_process_state = SKIP_TAG_PROCESS_STATE_MAX,
335 .tag_event = SKIP_TAG_EVENT_NONE,
336 .tag_proc_stage = SKIP_TAG_PROCESS_STAGE_NONE,
337};
338
339enum SkipTagProcStage skip_tag_process_get_proc_stage(void)
340{
341 return s_tag_process_info.tag_proc_stage;
342}
343
344void skip_tag_process_start(enum SkipTagEvent checked_tag_event)
345{
346 if (tag_process_sm.registered)
347 return;
348
349 s_tag_process_info.tags_timer = TM_ZERO;
350 s_tag_process_info.tag_process_state = SKIP_TAG_PROCESS_STATE_SEARCH;
351 s_tag_process_info.tag_event = checked_tag_event;
352 s_tag_process_info.tag_proc_stage = SKIP_TAG_PROCESS_STAGE_NONE;
353
355}
356
358{
359 // We're keeping track of the state separately so that we can restore it when
360 // we resume the state machine processing
361 s_tag_process_info.tag_process_state = tag_process_sm.state;
362 s_tag_process_info.tags_timer = TM_ZERO;
363 state_machine_remove(&tag_process_sm);
364}
365
367{
368 if (s_tag_process_info.tag_process_state >= SKIP_TAG_PROCESS_STATE_MAX)
369 return;
370
371 state_machine_register(&tag_process_sm);
372 state_machine_change_state(&tag_process_sm, s_tag_process_info.tag_process_state);
373}
374
375// Skip Tags will be fully processed over 61 frames (1 + 2 * TM_SKIP_TAG_ANIM_DURATION) so we have
376// time to process what's happening:
377// - Detect triggered Tag, executed instantly but will take a full frame given how state machines
378// work
379// - Starting the little bouncy animation as the Tag triggers
380// - Delete triggered Tag, serves as a little pause
381
382static SkipTag* s_consumed_tag = NULL;
383static int s_consumed_tag_idx = 0;
384static SkipTagCallback s_consumed_tag_effect_func = NULL;
385
386static void skip_tag_search_for_event(void)
387{
388 s_tag_process_info.tag_proc_stage = SKIP_TAG_PROCESS_STAGE_NONE;
389 s_tag_process_info.tags_timer++;
390
391 int nb_owned_tags = list_get_len(&s_owned_skip_tags);
392
393 for (; s_consumed_tag_idx < nb_owned_tags; s_consumed_tag_idx++)
394 {
395 s_consumed_tag = list_get_at_idx(&s_owned_skip_tags, s_consumed_tag_idx);
396
397 if (s_consumed_tag == NULL)
398 continue;
399
400 const SkipTagInfo* info = get_skip_tag_registry_entry(s_consumed_tag->type);
401
402 if (info == NULL || info->event_type != s_tag_process_info.tag_event)
403 continue;
404
405 // Return early in case the tag can trigger, will apply effect later
406 if (info->tag_condition_func())
407 {
408 s_consumed_tag_effect_func = info->tag_effect_func;
409 s_tag_process_info.tag_proc_stage = SKIP_TAG_PROCESS_STAGE_NONE;
410 state_machine_change_state(&tag_process_sm, SKIP_TAG_PROCESS_STATE_TRIGGER);
411 return;
412 }
413 }
414
415 s_consumed_tag_idx = 0;
416 s_tag_process_info.tag_proc_stage = SKIP_TAG_PROCESS_STAGE_END;
417 s_tag_process_info.tag_process_state = SKIP_TAG_PROCESS_STATE_MAX;
419}
420
421static void skip_tag_trigger_for_event(void)
422{
423 s_tag_process_info.tag_proc_stage = SKIP_TAG_PROCESS_STAGE_NONE;
424 if (s_tag_process_info.tags_timer++ % FRAMES(TM_SKIP_TAG_ANIM_DURATION) != TM_ZERO)
425 return;
426
427 BG_POINT tag_pos = {fx2int(s_consumed_tag->x), fx2int(s_consumed_tag->y)};
428
429 // Set tiles to the "activated" ones, each with colors that correspond to their tag type
430 s_consumed_tag->type += MAX_SKIP_TAG_TYPES;
431 skip_tag_set_sprite(s_consumed_tag, OWNED_SKIP_TAG_STARTING_LAYER + s_consumed_tag_idx);
432 sprite_object_position((SpriteObject*)s_consumed_tag, tag_pos.x, tag_pos.y);
433
434 // Reset rotation so that the sprite isn't too distorted when bouncing if
435 // it's still moving from a previous animation
436 s_consumed_tag->rotation = 0;
437 s_consumed_tag->vrotation = 0;
438 sprite_object_bounce((SpriteObject*)s_consumed_tag, SKIP_TAG_TRIGGER_BOUNCE_STRENGTH);
440
441 s_tag_process_info.tag_proc_stage = SKIP_TAG_PROCESS_STAGE_TRIGGER;
442 state_machine_change_state(&tag_process_sm, SKIP_TAG_PROCESS_STATE_REMOVE);
443
444 // Apply tag here so it matches the animation, but AFTER changing the processing state,
445 // else, with the Boss tag, the trigger state will be restored and `s_consumed_tag_effect_func`
446 // will be dereferenced after being set to NULL
447 (*s_consumed_tag_effect_func)();
448 s_consumed_tag = NULL;
449 s_consumed_tag_effect_func = NULL;
450}
451
452static void skip_tag_remove_for_event(void)
453{
454 s_tag_process_info.tag_proc_stage = SKIP_TAG_PROCESS_STAGE_NONE;
455 if (s_tag_process_info.tags_timer++ % FRAMES(TM_SKIP_TAG_ANIM_DURATION) != TM_ZERO)
456 return;
457
458 remove_skip_tag(s_consumed_tag_idx);
459 state_machine_change_state(&tag_process_sm, SKIP_TAG_PROCESS_STATE_SEARCH);
460}
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
Blind select state functions.
Game global game variables struct definition.
#define TILE_SIZE
Tile size in pixels, both height and width as tiles are square.
#define TILE_MEM_OBJ_CHARBLOCK0_IDX
The index for the first charblock of the object memory in tonc's tile_mem.
Functions relative to the handling of Jokers.
void * list_get_at_idx(List *list, unsigned int idx)
Definition list.c:219
int list_get_len(const List *list)
Definition list.c:214
ListItr rev_list_itr_create(List *list)
Definition list.c:269
void list_clear(List *list)
Definition list.c:44
void * list_itr_next(ListItr *itr)
Definition list.c:281
bool list_remove_at_idx(List *list, unsigned int idx)
Definition list.c:237
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.
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
void remove_skip_tag(int tag_idx)
Pops the Tag at index in owned list, destroys it and rearranges the remaining Tags' sprites.
Definition skip_tag.c:279
void set_owned_skip_tags_moved_offscreen(bool move_offscreen)
Show or hide the owned SkipTag sprites by moving them offscreen to the right.
Definition skip_tag.c:141
void skip_tag_process_pause(void)
Suspend the processing of Skip Tags.
Definition skip_tag.c:357
#define NB_ANTE1_SKIP_TAGS
Number of implemented Skip Tags available from Ante 1.
Definition skip_tag.c:40
void skip_tag_set_sprite(SkipTag *tag, s16 layer)
Set the sprite for the given SkipTag, according to its internal ID.
Definition skip_tag.c:94
void skip_tag_destroy(SkipTag **tag)
Destroy the given SkipTag, invalidate it in the corresponding memory pool and set the pointer provide...
Definition skip_tag.c:128
SkipTag * roll_skip_tag(void)
Create a new SkipTag with a random ID, picked by taking the Ante into account, as some Tags only appe...
Definition skip_tag.c:154
void skip_tag_process_start(enum SkipTagEvent checked_tag_event)
Start Skip Tag processing for the specified event.
Definition skip_tag.c:344
enum SkipTagProcStage skip_tag_process_get_proc_stage(void)
Recover the current stage of the Tag processing.
Definition skip_tag.c:339
void skip_tag_process_resume(void)
Resume the processing of Skip Tags.
Definition skip_tag.c:366
void add_skip_tag(SkipTag **blind_tag)
Adds the SkipTag to the owned list, snaps it into position and sets the original pointer to NULL.
Definition skip_tag.c:230
GBAL_UNUSED bool skip_tag_is_owned(enum SkipTagTypes tag_type)
Checks if the given tag type is present in owned Tags list.
Definition skip_tag.c:197
void remove_all_skip_tags(void)
Delete all owned Skip Tags.
Definition skip_tag.c:289
#define NB_SKIP_TAG_TYPES
Number of implemented Skip Tags.
Definition skip_tag.c:46
SkipTag * skip_tag_new(enum SkipTagTypes tag_type)
Create a new Skip Tag object for the given Tag ID and allocates space for it in the corresponding mem...
Definition skip_tag.c:83
int skip_tag_count(enum SkipTagTypes tag_type)
Counts how many tags of a certain type we own.
Definition skip_tag.c:213
List * get_owned_skip_tags(void)
Get the owned skip tags list.
Definition skip_tag.c:76
Data structures and functions related to the handling of Skip Tags.
SkipTagTypes
List of all Skip Tags, in the order they are in on their spritesheet. Not all of them are implemented...
Definition skip_tag.h:45
const SkipTagInfo * get_skip_tag_registry_entry(enum SkipTagTypes tag_type)
Get the registry entry info for the given Skip Tag type.
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:237
void sprite_object_destroy(SpriteObject *sprite_object)
Destroy SpriteObject.
Definition sprite.c:248
int sprite_get_tid(enum SpriteType sprite_type, s16 layer)
Get the tile index of a certain SpriteType at a certain layer.
Definition sprite.c:66
void sprite_object_bounce(SpriteObject *sprite_object, FIXED strength)
Make SpriteObject bounce by slightly increasing its size in an elastic way.
Definition sprite.c:416
void sprite_object_set_sprite(SpriteObject *sprite_object, Sprite *sprite)
Register a Sprite to an associated SpriteObject.
Definition sprite.c:256
void sprite_object_set_target(SpriteObject *sprite_object, BG_POINT to)
Set a SpriteObject's target position so that is slides across the screen between from the current pos...
Definition sprite.c:429
INLINE void sprite_object_position(SpriteObject *sprite_object, int x, int y)
Set sprite_object position. Inlined for efficiency.
Definition sprite.h:483
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:77
void sprite_destroy(Sprite **sprite)
Destroy Sprite.
Definition sprite.c:134
int sprite_get_starting_layer(enum SpriteType sprite_type)
Get the starting layer of a certain type of sprite.
Definition sprite.c:71
void sprite_object_sway(SpriteObject *sprite_object)
Make SpriteObject sway by slightly rotating on screen.
Definition sprite.c:423
State Machine.
void state_machine_remove(StateMachine *state_machine)
Remove a statemachine's update function.
void state_machine_register(StateMachine *state_machine)
Register a statemachine to run it's update function once per frame.
void state_machine_change_state(StateMachine *state_machine, int new_state)
Calls the current state's on_exit, the new state's on_init, and sets the active update fn.
An iterator into a list.
Definition list.h:91
A doubly-linked list.
Definition list.h:61
Data structure containing the info about a Skip Tag, namely:
Definition skip_tag.h:108
A sprite object is a sprite that is focusable and movable in animation.
Definition sprite.h:86
Sprite struct for GBA hardware specifics.
Definition sprite.h:54
State machine callbacks.
State machine instance.
bool registered
Flag to determine if statemachine is already running it's active_update function.
int state
The current state of the state machine, the offset into state_infos.
Timer constants.
Utilities relating around number string representation and protected arithmatic helper functions.
#define GBAL_RETURN_IF_NULL(param, ret_val)
Returns ret_val and prints a default error message if param is equal to NULL. Useful for checking arg...
Definition util.h:138
#define GBAL_UNUSED
Wrapper around the "unused" attribute.
Definition util.h:25
#define RET_NONE
An empty return value for RETURN_IF macros when used in void functions Expands to nothing because mac...
Definition util.h:146