Feature/game over audio (#494)
* initial additions * Move current_blind to game vars * tmp * tmp * tmp * tmp * tmp * tmp * tmp * Compile but is broken * temp fix for token location * tmp * Working, lets clean up * remove todo * Remove odd reroll blind variable * fix skipping blind screen * fix reroll * remove magic for blind token locations * clang format * Cleanup button highlight in blind menu * some cleanup * Add layout file for global UI rects * remove dead code * Clang format * clang format it up * Move reset top left funct to gfx utils * make note of soon-to-be removed vars * Cleanup gamevars declaration * Cleanup layout file * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Add include for stdbool.h in common_ui.h * Cleanup rect declaration * Start moving state machine stuff to new file * quick tmp * oops, add new files * working call list * fixed pointer stuff * working as per norm * working replacement * move defines to static const * fix compile issue * Add some missing "void"s * Working new substates * add statemachine to blind select * Fix game restart statemachine leak * clang-format * Clang format * Rework shop exit logic * Use new state machine in shop * Remove substates entirely * Remove debug stuff * Clang format * document state machine better * update list tests * clang-format * Update for PR * Claaaaang format * another clang-format * Remove extra state in round_end * cleanup docs for removals in lists * clang-format... again * update doxygen format * Clang format * tmp * Update for Pr * clang-format * Cleanup for only supporting losing * bug fix, thanks logs! * Fix dangling pointer bug The control of removing statemachines may call issues if starting the function is done from another state machine call. To remedy this, add a flag to determine if a state machine is registered and return early instead of blindly removing it to re-add. * Fix bug with audio iterations possibly going in reverse * Remove extra includes * Clang-format * Add sound effects for game win/lose * adjust audio steps * tmp * Nice sounding and cleaned up * Fix and cleanup * clang-format * clang-format * clang format again --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -6,6 +6,8 @@
|
||||
#ifndef AUDIO_UTILS_H
|
||||
#define AUDIO_UTILS_H
|
||||
|
||||
#include "game_variables.h"
|
||||
|
||||
#include <mm_types.h>
|
||||
|
||||
/**
|
||||
@@ -64,4 +66,25 @@
|
||||
*/
|
||||
void play_sfx(mm_word id, mm_word rate, mm_byte volume);
|
||||
|
||||
/**
|
||||
* @brief Play music at a low pitch and slow tempo (lose screen)
|
||||
*/
|
||||
void play_lose_music(void);
|
||||
|
||||
/**
|
||||
* @brief Play music at a normal pitch and tempo
|
||||
*/
|
||||
void play_regular_music(void);
|
||||
|
||||
void set_volume(int volume);
|
||||
|
||||
/**
|
||||
* @brief Get MaxMod module audio value from VOLUME_OPTION value
|
||||
* @param step VOLUME_OPTION between @ref VOLUME_OPTION_MIN and @ref VOLUME_OPTION_MAX
|
||||
*/
|
||||
inline int volume_module_step_to_val(unsigned char step)
|
||||
{
|
||||
return MM_MODULE_FULL_VOLUME * step / VOLUME_OPTION_MAX;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -66,6 +66,11 @@ typedef struct
|
||||
* @brief The current state of the state machine, the offset into state_infos
|
||||
*/
|
||||
int state;
|
||||
|
||||
/**
|
||||
* @brief Flag to determine if statemachine is already running it's active_update function
|
||||
*/
|
||||
bool registered;
|
||||
} StateMachine;
|
||||
|
||||
/**
|
||||
@@ -102,6 +107,14 @@ void noop(void);
|
||||
|
||||
// clang-format off
|
||||
#define STATE_INFO_UPDATE_FN_ONLY(fn) {.on_init = noop, .on_update = fn, .on_exit = noop}
|
||||
|
||||
#define STATE_MACHINE_DEFINE(infos, num) \
|
||||
{ \
|
||||
.state_infos = &infos[0], \
|
||||
.num_infos = num, \
|
||||
.registered = false, \
|
||||
};
|
||||
|
||||
// clang-format on
|
||||
|
||||
#endif // STATE_MACHINE_H
|
||||
|
||||
@@ -17,6 +17,14 @@
|
||||
|
||||
#define UNDEFINED -1
|
||||
|
||||
/**
|
||||
* @def SIGN
|
||||
* @brief Get the sign (signum) of an integer
|
||||
*
|
||||
* @return 1,-1,0 if the number is positive,negative, or 0, respectively.
|
||||
*/
|
||||
#define SIGN(x) ((x > 0) - (x < 0))
|
||||
|
||||
/**
|
||||
* @def NUM_ELEM_IN_ARR
|
||||
* @brief Get the number of elements in an array
|
||||
|
||||
+134
-1
@@ -1,17 +1,150 @@
|
||||
/**
|
||||
* @file audio_utils.c
|
||||
*
|
||||
* @brief Audio utility functions implementation.
|
||||
*/
|
||||
|
||||
#include "audio_utils.h"
|
||||
|
||||
#include "game_variables.h"
|
||||
#include "mgba_logger.h"
|
||||
#include "state_machine.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <maxmod.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
s32 target;
|
||||
s32 stride;
|
||||
} AudioParamReq;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
s32 current;
|
||||
AudioParamReq req;
|
||||
} AudioParam;
|
||||
|
||||
// clang-format off
|
||||
#define AUDIO_PARAM_REQ_DEFAULT { .target = 0, .stride = 0 }
|
||||
#define AUDIO_PARAM_DEFINE(init_val) { .current = init_val, .req = AUDIO_PARAM_REQ_DEFAULT }
|
||||
// clang-format on
|
||||
|
||||
typedef struct
|
||||
{
|
||||
AudioParam pitch;
|
||||
AudioParam tempo;
|
||||
AudioParam volume;
|
||||
} MusicPlayerState;
|
||||
|
||||
static const u32 DEFAULT_PITCH = 0x400;
|
||||
static const u32 DEFAULT_TEMPO = 0x400;
|
||||
static const u32 DEFAULT_VOLUME = MM_MODULE_FULL_VOLUME;
|
||||
static const u32 MUSIC_CHANGE_FRAMES = 75;
|
||||
static const u32 VOLUME_CHANGE_FRAMES = MUSIC_CHANGE_FRAMES / 3;
|
||||
|
||||
static void set_audio_param_req(AudioParam* param, s32 target, s32 steps);
|
||||
static void speed_change_update(void);
|
||||
|
||||
static MusicPlayerState music_player = {
|
||||
.pitch = AUDIO_PARAM_DEFINE(DEFAULT_PITCH),
|
||||
.tempo = AUDIO_PARAM_DEFINE(DEFAULT_TEMPO),
|
||||
.volume = AUDIO_PARAM_DEFINE(DEFAULT_VOLUME),
|
||||
};
|
||||
|
||||
static StateInfo state_info[] = {
|
||||
STATE_INFO_UPDATE_FN_ONLY(speed_change_update),
|
||||
};
|
||||
|
||||
static StateMachine song_speed_sm = STATE_MACHINE_DEFINE(state_info, 1);
|
||||
|
||||
static void set_audio_param_req(AudioParam* param, s32 target, s32 steps)
|
||||
{
|
||||
int offset = target - param->current;
|
||||
|
||||
// if '0' set to '1'
|
||||
steps |= !steps;
|
||||
param->req.stride = offset / steps;
|
||||
param->req.stride = !param->req.stride ? SIGN(offset) : param->req.stride;
|
||||
param->req.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the @ref AudioParam for the music transition state machine for audio transitions
|
||||
*
|
||||
* @return true if target is reached, false otherwise
|
||||
*/
|
||||
static inline bool audio_param_update(AudioParam* param)
|
||||
{
|
||||
if (abs(param->current - param->req.target) <= abs(param->req.stride))
|
||||
{
|
||||
param->current = param->req.target;
|
||||
return true;
|
||||
}
|
||||
param->current += param->req.stride;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void speed_change_update(void)
|
||||
{
|
||||
bool tempo_reached = audio_param_update(&music_player.tempo);
|
||||
bool pitch_reached = audio_param_update(&music_player.pitch);
|
||||
bool volume_reached = audio_param_update(&music_player.volume);
|
||||
|
||||
mmSetModuleTempo(music_player.tempo.current);
|
||||
mmSetModulePitch(music_player.pitch.current);
|
||||
set_volume(music_player.volume.current);
|
||||
|
||||
if (tempo_reached && pitch_reached && volume_reached)
|
||||
state_machine_remove(&song_speed_sm);
|
||||
}
|
||||
|
||||
void play_sfx(mm_word id, mm_word rate, mm_byte volume)
|
||||
{
|
||||
int adj_volume = volume * g_game_vars.sound_volume / VOLUME_OPTION_MAX;
|
||||
mm_sound_effect sfx = {
|
||||
{id},
|
||||
rate,
|
||||
0,
|
||||
(volume * g_game_vars.sound_volume) / VOLUME_OPTION_MAX,
|
||||
adj_volume,
|
||||
SFX_DEFAULT_PAN,
|
||||
};
|
||||
mmEffectEx(&sfx);
|
||||
}
|
||||
|
||||
void play_lose_music(void)
|
||||
{
|
||||
const u32 slow_music_speed = 0x200;
|
||||
// Don't adjust the volume if already on the lowest setting, otherwise it's not audible
|
||||
u32 vol = g_game_vars.music_volume;
|
||||
// Don't divide the audio by half if it's one, just sounds bad
|
||||
// This works too, not for negative numbers. It's neat.
|
||||
// vol |= !((vol - 1) > 0);
|
||||
vol = (vol == 1) ? 1 : vol / 2;
|
||||
u32 target_vol = volume_module_step_to_val(vol);
|
||||
|
||||
set_audio_param_req(&music_player.pitch, slow_music_speed, MUSIC_CHANGE_FRAMES);
|
||||
set_audio_param_req(&music_player.tempo, slow_music_speed, MUSIC_CHANGE_FRAMES);
|
||||
set_audio_param_req(&music_player.volume, target_vol, VOLUME_CHANGE_FRAMES);
|
||||
|
||||
state_machine_register(&song_speed_sm);
|
||||
state_machine_change_state(&song_speed_sm, 0);
|
||||
}
|
||||
|
||||
void play_regular_music(void)
|
||||
{
|
||||
u32 target_vol = volume_module_step_to_val(g_game_vars.music_volume);
|
||||
set_audio_param_req(&music_player.pitch, DEFAULT_PITCH, MUSIC_CHANGE_FRAMES);
|
||||
set_audio_param_req(&music_player.tempo, DEFAULT_TEMPO, MUSIC_CHANGE_FRAMES);
|
||||
set_audio_param_req(&music_player.volume, target_vol, VOLUME_CHANGE_FRAMES);
|
||||
|
||||
state_machine_register(&song_speed_sm);
|
||||
state_machine_change_state(&song_speed_sm, 0);
|
||||
}
|
||||
|
||||
void set_volume(int volume)
|
||||
{
|
||||
music_player.volume.current = volume;
|
||||
mmSetModuleVolume(volume);
|
||||
}
|
||||
|
||||
+1
-4
@@ -211,10 +211,7 @@ static StateInfo state_info[] = {
|
||||
#undef DEF_STATE_INFO
|
||||
};
|
||||
|
||||
static StateMachine game_sm = {
|
||||
.state_infos = &state_info[0],
|
||||
.num_infos = GAME_STATE_MAX,
|
||||
};
|
||||
static StateMachine game_sm = STATE_MACHINE_DEFINE(state_info, GAME_STATE_MAX);
|
||||
|
||||
// clang-format off
|
||||
static SelectionGridRow game_playing_selection_rows[] = {
|
||||
|
||||
@@ -56,10 +56,7 @@ static StateInfo state_info[] = {
|
||||
STATE_INFO_UPDATE_FN_ONLY(game_blind_select_exit),
|
||||
};
|
||||
|
||||
static StateMachine blind_select_sm = {
|
||||
.state_infos = &state_info[0],
|
||||
.num_infos = BLIND_SELECT_MAX,
|
||||
};
|
||||
static StateMachine blind_select_sm = STATE_MACHINE_DEFINE(state_info, BLIND_SELECT_MAX);
|
||||
|
||||
// clang-format off
|
||||
// Points x y
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "audio_utils.h"
|
||||
#include "button.h"
|
||||
#include "game.h"
|
||||
#include "game/main_menu.h"
|
||||
#include "graphic_utils.h"
|
||||
#include "layout.h"
|
||||
#include "random.h"
|
||||
@@ -63,11 +62,16 @@ static void game_over_init(enum EndCondition init_condition)
|
||||
|
||||
void game_win_on_init(void)
|
||||
{
|
||||
play_sfx(SFX_GAME_WIN, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
|
||||
|
||||
game_over_init(END_CONDITION_WIN);
|
||||
}
|
||||
|
||||
void game_lose_on_init(void)
|
||||
{
|
||||
play_sfx(SFX_GAME_LOSE, MM_BASE_PITCH_RATE, SFX_DEFAULT_VOLUME);
|
||||
play_lose_music();
|
||||
|
||||
game_over_init(END_CONDITION_LOSS);
|
||||
}
|
||||
|
||||
@@ -127,6 +131,7 @@ void game_over_on_update(void)
|
||||
|
||||
void game_over_on_exit(void)
|
||||
{
|
||||
play_regular_music();
|
||||
condition = END_CONDITION_NONE;
|
||||
game_reset();
|
||||
rng_shuffle_seed();
|
||||
|
||||
@@ -612,7 +612,7 @@ static bool music_volume_row_on_selection_changed(
|
||||
}
|
||||
|
||||
update_volume_slider_graphics(MUSIC_VOLUME_ROW_IDX);
|
||||
mmSetModuleVolume(MM_MODULE_FULL_VOLUME * g_game_vars.music_volume / VOLUME_OPTION_MAX);
|
||||
set_volume(volume_module_step_to_val(g_game_vars.music_volume));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -79,10 +79,7 @@ static StateInfo state_info[] = {
|
||||
STATE_INFO_UPDATE_FN_ONLY(game_round_end_dismiss_round_end_panel),
|
||||
};
|
||||
|
||||
static StateMachine round_end_sm = {
|
||||
.state_infos = &state_info[0],
|
||||
.num_infos = ROUND_END_STATES_MAX,
|
||||
};
|
||||
static StateMachine round_end_sm = STATE_MACHINE_DEFINE(state_info, ROUND_END_STATES_MAX);
|
||||
|
||||
static int calculate_interest_reward(void)
|
||||
{
|
||||
|
||||
+1
-4
@@ -81,10 +81,7 @@ static StateInfo shop_state_actions[] = {
|
||||
STATE_INFO_UPDATE_FN_ONLY(game_shop_outro),
|
||||
};
|
||||
|
||||
static StateMachine shop_sm = {
|
||||
.state_infos = &shop_state_actions[0],
|
||||
.num_infos = GAME_SHOP_MAX,
|
||||
};
|
||||
static StateMachine shop_sm = STATE_MACHINE_DEFINE(shop_state_actions, GAME_SHOP_MAX);
|
||||
|
||||
// Shop SelectionGrid
|
||||
|
||||
|
||||
+2
-2
@@ -7,6 +7,7 @@
|
||||
#include "bitset.h"
|
||||
#include "card.h"
|
||||
#include "game.h"
|
||||
#include "game_variables.h"
|
||||
#include "joker.h"
|
||||
#include "list.h"
|
||||
#include "util.h"
|
||||
@@ -326,10 +327,9 @@ void load_options(void)
|
||||
g_game_vars.music_volume = options.music_volume;
|
||||
g_game_vars.sound_volume = options.sound_volume;
|
||||
|
||||
set_volume(volume_module_step_to_val(g_game_vars.music_volume));
|
||||
set_cards_high_contrast(options.cards_high_contrast);
|
||||
set_cards_more_readable(options.cards_more_readable);
|
||||
|
||||
mmSetModuleVolume(MM_MODULE_FULL_VOLUME * g_game_vars.music_volume / VOLUME_OPTION_MAX);
|
||||
}
|
||||
|
||||
void save_game(void)
|
||||
|
||||
+11
-3
@@ -15,9 +15,10 @@ void noop(void) {};
|
||||
|
||||
void state_machine_register(StateMachine* state_machine)
|
||||
{
|
||||
// Always try to remove the state machine first. Only one can exist at a time
|
||||
// So ensure that calling this function doesn't add two update functions
|
||||
state_machine_remove(state_machine);
|
||||
if (state_machine->registered)
|
||||
return;
|
||||
|
||||
state_machine->registered = true;
|
||||
|
||||
state_machine->active_update = noop;
|
||||
state_machine->state = UNDEFINED;
|
||||
@@ -27,6 +28,10 @@ void state_machine_register(StateMachine* state_machine)
|
||||
|
||||
void state_machine_remove(StateMachine* state_machine)
|
||||
{
|
||||
if (!state_machine->registered)
|
||||
return;
|
||||
|
||||
state_machine->registered = false;
|
||||
list_remove_data(&update_cbs, &state_machine->active_update);
|
||||
}
|
||||
|
||||
@@ -42,6 +47,9 @@ void state_machine_update(void)
|
||||
|
||||
void state_machine_change_state(StateMachine* state_machine, int new_state)
|
||||
{
|
||||
if (!state_machine->registered)
|
||||
return;
|
||||
|
||||
if (state_machine->state >= 0 && state_machine->state < state_machine->num_infos)
|
||||
{
|
||||
state_machine->state_infos[state_machine->state].on_exit();
|
||||
|
||||
Reference in New Issue
Block a user