diff --git a/resources/00-taisei.pkgdir/bgm/bonus0.bgm b/resources/00-taisei.pkgdir/bgm/bonus0.bgm index be7ea533..eba7dfe5 100644 --- a/resources/00-taisei.pkgdir/bgm/bonus0.bgm +++ b/resources/00-taisei.pkgdir/bgm/bonus0.bgm @@ -1,4 +1,5 @@ title = Divine Correction for Outliers artist = Tuck V loop = res/bgm/bonus0.ogg -loop_point = 2.909093 \ No newline at end of file +loop_point = 2.909093 +comment = The decision to arrange a character’s existing theme or to come up with something entirely new is difficult and likely one ZUN himself struggles with.\n\nI know my strengths lie in the latter, which ultimately resulted in the stormy composition usually heard at the top of the tower, but Iku has such an interesting original theme I had to at least accept the challenge.\n\nUltimately, I can’t decide if I did the original justice with this one or not, but I know it holds a special place in my heart. diff --git a/resources/00-taisei.pkgdir/bgm/credits.bgm b/resources/00-taisei.pkgdir/bgm/credits.bgm index a3ecfe0b..0bc0c98a 100644 --- a/resources/00-taisei.pkgdir/bgm/credits.bgm +++ b/resources/00-taisei.pkgdir/bgm/credits.bgm @@ -1,3 +1,4 @@ title = Existential Field artist = Tuck V -intro = res/bgm/credits.ogg +loop = res/bgm/credits.ogg +comment = The staff roll theme.\n\nA sense of inspiration and the future is maintained from Excitation Field.\n\nOne that’s a lot gentler than a thunderstorm… perhaps a bit more like a rainbow. diff --git a/resources/00-taisei.pkgdir/bgm/ending.bgm b/resources/00-taisei.pkgdir/bgm/ending.bgm index 210763f2..e14ac24d 100644 --- a/resources/00-taisei.pkgdir/bgm/ending.bgm +++ b/resources/00-taisei.pkgdir/bgm/ending.bgm @@ -1,3 +1,4 @@ title = Dream Cycle artist = Tuck V loop = res/bgm/ending.ogg +comment = The ending’s theme.\n\nIn summation, class, what have we learned today?\n\nKnowledge is the forbidden fruit; from it stem the powers of light and darkness. But it’s about time the protagonists have had a snack. diff --git a/resources/00-taisei.pkgdir/bgm/menu.bgm b/resources/00-taisei.pkgdir/bgm/menu.bgm index 7162b7ff..11307c2b 100644 --- a/resources/00-taisei.pkgdir/bgm/menu.bgm +++ b/resources/00-taisei.pkgdir/bgm/menu.bgm @@ -1,4 +1,5 @@ title = Delta Concision artist = Tuck V loop = res/bgm/menu.ogg -loop_point = 13.71 \ No newline at end of file +loop_point = 13.71 +comment = An introduction to the occident.\n\nMany paths branch from one. Echoes from across the sea yield a new adventure. diff --git a/resources/00-taisei.pkgdir/bgm/scuttle.bgm b/resources/00-taisei.pkgdir/bgm/scuttle.bgm index e7e9db9d..dcf0c0a9 100644 --- a/resources/00-taisei.pkgdir/bgm/scuttle.bgm +++ b/resources/00-taisei.pkgdir/bgm/scuttle.bgm @@ -2,3 +2,4 @@ title = Logic Bombardier artist = Tuck V loop = res/bgm/scuttle.ogg loop_point = 5.64 +comment = A secret theme for a most beloved character…\n\nI wanted to play with the concept of Scuttle’s motif as heard in the tunnel of light and try to apply it to a full-length song.\n\nThe results were interesting! Her theme is as much an experiment as her existence.\n\nSupposing she were to appear as a boss, I think it might sound different given a different role in the incident. diff --git a/resources/00-taisei.pkgdir/bgm/stage1.bgm b/resources/00-taisei.pkgdir/bgm/stage1.bgm index bdd02dd5..0632fa75 100644 --- a/resources/00-taisei.pkgdir/bgm/stage1.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage1.bgm @@ -1,4 +1,5 @@ title = The Fog Invites Unseen Mischief artist = Tuck V loop = res/bgm/stage1/stage.ogg -loop_point = 6.857143 \ No newline at end of file +loop_point = 6.857143 +comment = The first stage’s theme.\n\nA relaxing mist illustrated with subdued instruments. Familiar shadows stir trouble just out of view. Ripples of times past dance across Misty Lake. diff --git a/resources/00-taisei.pkgdir/bgm/stage1boss.bgm b/resources/00-taisei.pkgdir/bgm/stage1boss.bgm index f1d7db2e..d08c4a04 100644 --- a/resources/00-taisei.pkgdir/bgm/stage1boss.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage1boss.bgm @@ -1,4 +1,5 @@ title = An Impish Reflection on the Water artist = Tuck V loop = res/bgm/stage1/boss.ogg -loop_point = 3.200000 \ No newline at end of file +loop_point = 3.200000 +comment = Cirno’s theme.\n\nShe’s feeling just as brave this time around, and acting a fair bit stronger.\n\nHowever, she’s really just a minor annoyance, cold and brittle. The protagonists brush off her pranks and move on. diff --git a/resources/00-taisei.pkgdir/bgm/stage2.bgm b/resources/00-taisei.pkgdir/bgm/stage2.bgm index bc8893a9..403b44fe 100644 --- a/resources/00-taisei.pkgdir/bgm/stage2.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage2.bgm @@ -1,4 +1,5 @@ title = Treasure the Wager, Treasure the Odds artist = Tuck V loop = res/bgm/stage2/stage.ogg -loop_point = 10.124989 \ No newline at end of file +loop_point = 10.124989 +comment = The second stage’s theme.\n\nFor such a calm and serene path, there sure are a lot of troublemakers — a deceptively fast-paced theme, to be sure. One excitable example makes a brief appearance. The rhythm of this theme is meant to make you feel lucky. diff --git a/resources/00-taisei.pkgdir/bgm/stage2boss.bgm b/resources/00-taisei.pkgdir/bgm/stage2boss.bgm index 25c571a8..738dd713 100644 --- a/resources/00-taisei.pkgdir/bgm/stage2boss.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage2boss.bgm @@ -1,3 +1,4 @@ title = The Cheerful Presence of a Dark God artist = Tuck V -loop = res/bgm/stage2/boss.ogg \ No newline at end of file +loop = res/bgm/stage2/boss.ogg +comment = Hina Kagiyama’s theme.\n\nThis time, it’s all up to chance.\n\nIt’s a little merciful sometimes, but you still have to do your best, no matter what. Gambling might be a poor stress-reliever… diff --git a/resources/00-taisei.pkgdir/bgm/stage3.bgm b/resources/00-taisei.pkgdir/bgm/stage3.bgm index 1ad3f4d5..ccc093e8 100644 --- a/resources/00-taisei.pkgdir/bgm/stage3.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage3.bgm @@ -1,3 +1,4 @@ -title = Fizeau's Finding - Lightray Vector +title = Fizeau’s Finding - Lightray Vector artist = Tuck V -loop = res/bgm/stage3/stage.ogg \ No newline at end of file +loop = res/bgm/stage3/stage.ogg +comment = The third stage’s theme.\n\nIt’s usually tricky to see in the dark. It pulls you in, engulfs you, like a sense-smothering flame. But this time, it might be best to protect your eyes. A fluttering little bug feels most at home in the dead of night, and most alive in a sea of light. diff --git a/resources/00-taisei.pkgdir/bgm/stage3boss.bgm b/resources/00-taisei.pkgdir/bgm/stage3boss.bgm index 29c2a2c8..787d99fe 100644 --- a/resources/00-taisei.pkgdir/bgm/stage3boss.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage3boss.bgm @@ -1,4 +1,5 @@ title = Lightningbug ~ Lightning Heart artist = Tuck V loop = res/bgm/stage3/boss.ogg -loop_point = 1.500000 \ No newline at end of file +loop_point = 1.500000 +comment = Wriggle Nightbug’s theme.\n\nA very motivated bug. She’s at the top of the world! — No, someone else is, she’s in the middle of a tunnel. Regardless, she knows she can do anything if she puts her mind to it, right?\n\nSounds a little “beach”-like in the introduction… diff --git a/resources/00-taisei.pkgdir/bgm/stage4.bgm b/resources/00-taisei.pkgdir/bgm/stage4.bgm index 87de6e74..20dddf56 100644 --- a/resources/00-taisei.pkgdir/bgm/stage4.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage4.bgm @@ -1,4 +1,5 @@ title = Evidence of Ki Domestication artist = Tuck V loop = res/bgm/stage4/stage.ogg -loop_point = 32.470590 \ No newline at end of file +loop_point = 32.470590 +comment = The fourth stage’s theme.\n\nAnother trip back into the orient… like a pendulum swinging back and forth.\n\nThis theme draws inspiration from a particularly elusive musician living on the other side of the sea known for the Chinese influences in his music.\n\nSmells like rust. diff --git a/resources/00-taisei.pkgdir/bgm/stage4boss.bgm b/resources/00-taisei.pkgdir/bgm/stage4boss.bgm index 18439e31..8c8cb998 100644 --- a/resources/00-taisei.pkgdir/bgm/stage4boss.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage4boss.bgm @@ -1,4 +1,5 @@ title = Marginal Red ~ The Red Word artist = Tuck V loop = res/bgm/stage4/boss.ogg -loop_point = 24.000000 \ No newline at end of file +loop_point = 24.000000 +comment = Kurumi’s theme.\n\nOld faces and new dreams. She’s taken a new residence, something a little more comfortable than a Lake of Blood, but this confrontation is anything but. A mix of fears make the walls feel like they’re closing in. diff --git a/resources/00-taisei.pkgdir/bgm/stage5.bgm b/resources/00-taisei.pkgdir/bgm/stage5.bgm index 19b55d18..bb10bc33 100644 --- a/resources/00-taisei.pkgdir/bgm/stage5.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage5.bgm @@ -1,3 +1,4 @@ title = Speak Not Falsely ~ Excitation Field artist = Tuck V -loop = res/bgm/stage5/stage.ogg \ No newline at end of file +loop = res/bgm/stage5/stage.ogg +comment = The fifth stage’s theme.\n\nAnd back again… how did that song go? “No reason to get excited…”\n\nBut in honesty, this is a combination of Eastern and Western influences now. If it were like a pendulum up to this point, then this theme is like an oscillating electric current.\n\nThose who climb risk a long fall, so look to the top and don’t make a single dishonest step. diff --git a/resources/00-taisei.pkgdir/bgm/stage5boss.bgm b/resources/00-taisei.pkgdir/bgm/stage5boss.bgm index 349000e5..eac590c9 100644 --- a/resources/00-taisei.pkgdir/bgm/stage5boss.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage5boss.bgm @@ -1,4 +1,5 @@ title = A Prayer Worth Three Hundred Coulombs artist = Tuck V loop = res/bgm/stage5/boss.ogg -loop_point = 12.80 \ No newline at end of file +loop_point = 12.80 +comment = Iku Nagae’s theme.\n\nThis song went through the wringer. Perhaps that’s appropriate for what the protagonists have to do. Imagine climbing a tower in the middle of a vicious thunderstorm and try not to feel exhausted.\n\nIku provides the first tribulation. diff --git a/resources/00-taisei.pkgdir/bgm/stage6.bgm b/resources/00-taisei.pkgdir/bgm/stage6.bgm index 37533318..9c43c1ce 100644 --- a/resources/00-taisei.pkgdir/bgm/stage6.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage6.bgm @@ -2,3 +2,4 @@ title = Summit of Revelations artist = Tuck V loop = res/bgm/stage6/stage.ogg loop_point = 13.71 +comment = The sixth stage’s theme.\n\nAt the top of the tower, you can see the world, and at what risk?\n\nA grand architectural marvel may take thousands of years to complete… this song was no different! But the end result feels like a great discovery. diff --git a/resources/00-taisei.pkgdir/bgm/stage6boss_phase1.bgm b/resources/00-taisei.pkgdir/bgm/stage6boss_phase1.bgm index 18dfc56b..593fc9cb 100644 --- a/resources/00-taisei.pkgdir/bgm/stage6boss_phase1.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage6boss_phase1.bgm @@ -1,3 +1,4 @@ title = Cosmological Battle ~ The Vacuum Catastrophe artist = Tuck V loop = res/bgm/stage6/boss_phase1.ogg +comment = Elly’s first theme.\n\nAn old friend. I always found myself inspired by the idea of a completely inconsequential character from forgotten times climbing to great heights after fading into obscurity.\n\nShe may have been a lowly gate guard the last time you saw her, but the apple’s fallen on her head. She’s a guard of a different kind of gate now. diff --git a/resources/00-taisei.pkgdir/bgm/stage6boss_phase2.bgm b/resources/00-taisei.pkgdir/bgm/stage6boss_phase2.bgm index d04860ec..7be0c065 100644 --- a/resources/00-taisei.pkgdir/bgm/stage6boss_phase2.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage6boss_phase2.bgm @@ -2,3 +2,4 @@ title = Deified Emergent Property ~ Ambivalent Soul artist = Tuck V loop = res/bgm/stage6/boss_phase2.ogg loop_point = 1.6 +comment = Elly’s second theme.\n\nThe most frightening and wondrous powers of the world remain just out of reach to our mortal minds. But a couple of truly brilliant people just may have found the bridge between our world and theirs…\n\nElly is a new person now, so an arrange wouldn’t speak the truth. diff --git a/resources/00-taisei.pkgdir/bgm/stage6boss_phase3.bgm b/resources/00-taisei.pkgdir/bgm/stage6boss_phase3.bgm index 4c012373..3a893155 100644 --- a/resources/00-taisei.pkgdir/bgm/stage6boss_phase3.bgm +++ b/resources/00-taisei.pkgdir/bgm/stage6boss_phase3.bgm @@ -2,3 +2,4 @@ title = Immutable Truth artist = Tuck V loop = res/bgm/stage6/boss_phase3.ogg loop_point = 35.64 +comment = Elly’s last spell.\n\nThe most ambitious and impossibly necessary tasks must encapsulate absolutely everything. Many think of science as a body of knowledge, a sealed bubble, within which all “great discoveries” have already been achieved, but the contrary could not be more true. Science is an unending series of dark curtains to pull back, each new finding more magnificent and imperceptible than the last. diff --git a/src/audio/audio.c b/src/audio/audio.c index 7c974ab0..fa5abfff 100644 --- a/src/audio/audio.c +++ b/src/audio/audio.c @@ -194,10 +194,9 @@ static void load_config_files(void) { parse_keyvalue_file_cb(SFX_PATH_PREFIX "volumes.conf", store_sfx_volume, NULL); } -static inline char* get_bgm_desc(char *name) { - Music *music = get_music(name); - assert(music != NULL); - return music->title; +static inline char* get_bgm_title(char *name) { + MusicMetadata *meta = get_resource_data(RES_BGM_METADATA, name, RESF_OPTIONAL); + return meta ? meta->title : NULL; } int get_default_sfx_volume(const char *sfx) { @@ -289,7 +288,7 @@ void start_bgm(const char *name) { } // Support drawing BGM title in game loop (only when music changed!) - if((current_bgm.title = get_bgm_desc(current_bgm.name)) != NULL) { + if((current_bgm.title = get_bgm_title(current_bgm.name)) != NULL) { current_bgm.started_at = global.frames; // Boss BGM title color may differ from the one at beginning of stage current_bgm.isboss = strendswith(current_bgm.name, "boss"); diff --git a/src/audio/audio.h b/src/audio/audio.h index a773d259..33d59cc8 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -13,6 +13,7 @@ #include "resource/sfx.h" #include "resource/bgm.h" +#include "resource/bgm_metadata.h" #define LOOPTIMEOUTFRAMES 10 #define DEFAULT_SFX_VOLUME 100 @@ -35,8 +36,8 @@ typedef struct Sound { } Sound; typedef struct Music { - char *title; MusicImpl *impl; + MusicMetadata *meta; } Music; typedef struct CurrentBGM { diff --git a/src/boss.c b/src/boss.c index 94877eed..2c386f58 100644 --- a/src/boss.c +++ b/src/boss.c @@ -883,6 +883,11 @@ void boss_finish_current_attack(Boss *boss) { if(p) { ++p->num_cleared; } + + // HACK + if(boss->current->info == &stage5_spells.extra.overload) { + stage_unlock_bgm("bonus0"); + } } else if(boss->current->type != AT_ExtraSpell) { boss->failed_spells++; } diff --git a/src/credits.c b/src/credits.c index 4e37c388..6bb2b46b 100644 --- a/src/credits.c +++ b/src/credits.c @@ -440,6 +440,7 @@ static RenderFrameAction credits_render_frame(void *arg) { static void credits_end_loop(void *ctx) { credits_free(); + progress_unlock_bgm("credits"); run_call_chain(&credits.cc, NULL); } diff --git a/src/ending.c b/src/ending.c index ee06f544..c31c8f68 100644 --- a/src/ending.c +++ b/src/ending.c @@ -281,6 +281,7 @@ static void ending_loop_end(void *ctx) { CallChain cc = e->cc; free_ending(e); free(e); + progress_unlock_bgm("ending"); run_call_chain(&cc, NULL); } diff --git a/src/menu/common.c b/src/menu/common.c index ae887199..f1e3372d 100644 --- a/src/menu/common.c +++ b/src/menu/common.c @@ -203,12 +203,13 @@ void draw_menu_title(MenuData *m, const char *title) { }); } -void draw_menu_list(MenuData *m, float x, float y, void (*draw)(void*, int, int)) { +void draw_menu_list(MenuData *m, float x, float y, void (*draw)(MenuEntry*, int, int), float scroll_threshold) { r_mat_push(); - float offset = ((((m->ecount+5) * 20) > SCREEN_H)? min(0, SCREEN_H * 0.7 - y - m->drawdata[2]) : 0); + float offset = smoothmin(0, scroll_threshold * 0.8 - y - m->drawdata[2], 80); r_mat_translate(x, y + offset, 0); draw_menu_selector(m->drawdata[0], m->drawdata[2], m->drawdata[1], 34, m->frames); + ShaderProgram *text_shader = r_shader_get("text_default"); for(int i = 0; i < m->ecount; ++i) { MenuEntry *e = &(m->entries[i]); @@ -235,10 +236,10 @@ void draw_menu_list(MenuData *m, float x, float y, void (*draw)(void*, int, int) if(draw && i < m->ecount-1) { draw(e, i, m->ecount); } else if(e->name) { - ShaderProgram *sh_prev = r_shader_current(); - r_shader("text_default"); - text_draw(e->name, &(TextParams) { .pos = { 20 - e->drawdata, 20*i } }); - r_shader_ptr(sh_prev); + text_draw(e->name, &(TextParams) { + .pos = { 20 - e->drawdata, 20*i }, + .shader_ptr = text_shader, + }); } } @@ -247,7 +248,7 @@ void draw_menu_list(MenuData *m, float x, float y, void (*draw)(void*, int, int) void animate_menu_list_entry(MenuData *m, int i) { MenuEntry *e = &(m->entries[i]); - e->drawdata += 0.2 * (10*(i == m->cursor) - e->drawdata); + fapproach_asymptotic_p(&e->drawdata, 10 * (i == m->cursor), 0.2, 1e-4); } void animate_menu_list_entries(MenuData *m) { @@ -260,9 +261,9 @@ void animate_menu_list(MenuData *m) { MenuEntry *s = m->entries + m->cursor; int w = text_width(get_font("standard"), s->name, 0); - m->drawdata[0] += (10 + w/2.0 - m->drawdata[0])/10.0; - m->drawdata[1] += (w*2 - m->drawdata[1])/10.0; - m->drawdata[2] += (20*m->cursor - m->drawdata[2])/10.0; + fapproach_asymptotic_p(&m->drawdata[0], 10 + w * 0.5, 0.1, 1e-5); + fapproach_asymptotic_p(&m->drawdata[1], w * 2, 0.1, 1e-5); + fapproach_asymptotic_p(&m->drawdata[2], 20 * m->cursor, 0.1, 1e-5); animate_menu_list_entries(m); } diff --git a/src/menu/common.h b/src/menu/common.h index a388ff58..fb9150a7 100644 --- a/src/menu/common.h +++ b/src/menu/common.h @@ -17,7 +17,7 @@ void start_game(MenuData *m, void *arg); void start_game_no_difficulty_menu(MenuData *m, void *arg); void draw_menu_selector(float x, float y, float w, float h, float t); void draw_menu_title(MenuData *m, const char *title); -void draw_menu_list(MenuData *m, float x, float y, void (*draw)(void*, int, int)); +void draw_menu_list(MenuData *m, float x, float y, void (*draw)(MenuEntry*, int, int), float scroll_threshold); void animate_menu_list(MenuData *m); void animate_menu_list_entries(MenuData *m); void animate_menu_list_entry(MenuData *m, int i); diff --git a/src/menu/mainmenu.c b/src/menu/mainmenu.c index 5273d81a..28d72e4a 100644 --- a/src/menu/mainmenu.c +++ b/src/menu/mainmenu.c @@ -109,6 +109,7 @@ MenuData* create_main_menu(void) { add_menu_entry(m, "Select Stage", menu_action_enter_stagemenu, NULL); #endif add_menu_entry(m, "Replays", menu_action_enter_replayview, NULL); + add_menu_entry(m, "Music Room", menu_action_enter_musicroom, NULL); add_menu_entry(m, "Options", menu_action_enter_options, NULL); #ifndef __EMSCRIPTEN__ add_menu_entry(m, "Quit", menu_action_close, NULL)->transition = TransFadeBlack; @@ -119,6 +120,7 @@ MenuData* create_main_menu(void) { spell_practice_entry = m->entries + 3; main_menu_update_practice_menus(); + progress_unlock_bgm("menu"); start_bgm("menu"); return m; @@ -134,7 +136,7 @@ void draw_main_menu(MenuData *menu) { draw_sprite(150.5, 100, "menu/logo"); r_mat_push(); - r_mat_translate(0, SCREEN_H-270, 0); + r_mat_translate(0, SCREEN_H - 15 - 35 * menu->ecount, 0); draw_menu_selector(50 + menu->drawdata[1]/2, menu->drawdata[2], 1.5 * menu->drawdata[1], 64, menu->frames); r_shader("text_default"); diff --git a/src/menu/meson.build b/src/menu/meson.build index 8d522f35..46bd6176 100644 --- a/src/menu/meson.build +++ b/src/menu/meson.build @@ -7,6 +7,7 @@ menu_src = files( 'ingamemenu.c', 'mainmenu.c', 'menu.c', + 'musicroom.c', 'options.c', 'replayview.c', 'savereplay.c', diff --git a/src/menu/musicroom.c b/src/menu/musicroom.c new file mode 100644 index 00000000..5ce639b0 --- /dev/null +++ b/src/menu/musicroom.c @@ -0,0 +1,166 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "musicroom.h" +#include "resource/resource.h" +#include "resource/bgm_metadata.h" +#include "resource/font.h" +#include "audio/audio.h" +#include "progress.h" +#include "common.h" +#include "options.h" +#include "renderer/api.h" +#include "video.h" + +static void musicroom_logic(MenuData *m) { + animate_menu_list(m); + fapproach_asymptotic_p(&m->drawdata[3], m->entries[m->cursor].arg != NULL, 0.2, 1e-5); +} + +static void musicroom_draw_item(MenuEntry *e, int i, int cnt) { + if(!e->name) { + return; + } + + text_draw(e->name, &(TextParams) { + .pos = { 20 - e->drawdata, 20 * i }, + .shader = "text_default", + }); + + if( + e->arg && + current_bgm.music && + current_bgm.music->meta && + current_bgm.music->meta == get_resource_data(RES_BGM_METADATA, e->arg, RESF_OPTIONAL) + ) { + text_draw("Now playing", &(TextParams) { + .pos = { SCREEN_W - 200, 20 * i }, + .shader = "text_default", + .align = ALIGN_RIGHT, + .color = RGBA(0.1, 0.6, 0.8, 0.8), + }); + } +} + +static void musicroom_draw(MenuData *m) { + r_state_push(); + draw_options_menu_bg(m); + draw_menu_title(m, "Music Room"); + draw_menu_list(m, 100, 100, musicroom_draw_item, SCREEN_H / GOLDEN_RATIO); + + float comment_height = SCREEN_H * (1 - 1 / GOLDEN_RATIO); + float comment_alpha = (1 - menu_fade(m)) * m->drawdata[3]; + float comment_offset = smoothstep(0, 1, (1 - comment_alpha)) * comment_height; + + r_shader_standard_notex(); + r_mat_push(); + r_mat_translate(SCREEN_W * 0.5, SCREEN_H - comment_height * 0.5 + comment_offset, 0); + r_mat_scale(SCREEN_W, comment_height, 1); + r_color4(0, 0, 0, 0.6 * comment_alpha); + r_draw_quad(); + r_mat_pop(); + r_state_pop(); + + Font *const text_font = get_font("standard"); + ShaderProgram *const text_shader = r_shader_get("text_default"); + const float text_x = 50; + const float text_y = SCREEN_H - comment_height + font_get_lineskip(text_font) * 1.5 + comment_offset; + + for(int i = 0; i < m->ecount; ++i) { + MenuEntry *e = m->entries + i; + float a = e->drawdata / 10.0 * comment_alpha; + + if(a < 0.05 || !e->arg) { + continue; + } + + const char *comment; + MusicMetadata *meta = get_resource_data(RES_BGM_METADATA, e->arg, RESF_OPTIONAL); + + if(meta && meta->comment) { + comment = meta->comment; + } else { + comment = "No comment available"; + } + + text_draw_wrapped(comment, SCREEN_W - text_x * 2, &(TextParams) { + .pos = { text_x, text_y }, + .font_ptr = text_font, + .shader_ptr = text_shader, + .color = RGBA(a, a, a, a), + }); + + if(meta->artist) { + const char *prefix = "— "; + char buf[strlen(prefix) + strlen(meta->artist) + 1]; + strcpy(buf, prefix); + strcat(buf, meta->artist); + + text_draw(buf, &(TextParams) { + .pos = { SCREEN_W - text_x, SCREEN_H + comment_offset - font_get_lineskip(text_font) }, + .font_ptr = text_font, + .shader_ptr = text_shader, + .color = RGBA(a, a, a, a), + .align = ALIGN_RIGHT, + }); + } + } +} + +static void action_play_bgm(MenuData *m, void *arg) { + preload_resource(RES_BGM, arg, RESF_OPTIONAL); + start_bgm(arg); +} + +static void add_bgm(MenuData *m, const char *bgm) { + if(progress_is_bgm_unlocked(bgm)) { + MusicMetadata *meta = get_resource_data(RES_BGM_METADATA, bgm, RESF_OPTIONAL | RESF_PRELOAD); + MenuEntry *e = add_menu_entry(m, (meta && meta->title) ? meta->title : "Unknown track", action_play_bgm, (void*)bgm); + e->transition = NULL; + } else { + add_menu_entry(m, "???????", NULL, NULL); + } +} + +MenuData* create_musicroom_menu(void) { + MenuData *m = alloc_menu(); + m->logic = musicroom_logic; + m->draw = musicroom_draw; + m->transition = TransMenuDark; + m->flags = MF_Abortable; + + add_bgm(m, "menu"); + add_bgm(m, "stage1"); + add_bgm(m, "stage1boss"); + add_bgm(m, "stage2"); + add_bgm(m, "stage2boss"); + add_bgm(m, "stage3"); + add_bgm(m, "stage3boss"); + add_bgm(m, "scuttle"); + add_bgm(m, "stage4"); + add_bgm(m, "stage4boss"); + add_bgm(m, "stage5"); + add_bgm(m, "stage5boss"); + add_bgm(m, "bonus0"); + add_bgm(m, "stage6"); + add_bgm(m, "stage6boss_phase1"); + add_bgm(m, "stage6boss_phase2"); + add_bgm(m, "stage6boss_phase3"); + add_bgm(m, "ending"); + add_bgm(m, "credits"); + add_menu_separator(m); + add_menu_entry(m, "Back", menu_action_close, NULL); + + while(!m->entries[m->cursor].action) { + ++m->cursor; + } + + return m; +} diff --git a/src/menu/musicroom.h b/src/menu/musicroom.h new file mode 100644 index 00000000..1b6f8a1d --- /dev/null +++ b/src/menu/musicroom.h @@ -0,0 +1,18 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#ifndef IGUARD_menu_musicroom_h +#define IGUARD_menu_musicroom_h + +#include "taisei.h" + +#include "menu.h" + +MenuData* create_musicroom_menu(void); + +#endif // IGUARD_menu_musicroom_h diff --git a/src/menu/replayview.c b/src/menu/replayview.c index c269be4a..f28c680f 100644 --- a/src/menu/replayview.c +++ b/src/menu/replayview.c @@ -346,7 +346,7 @@ static void replayview_draw(MenuData *m) { draw_options_menu_bg(m); draw_menu_title(m, "Replays"); - draw_menu_list(m, 50, 100, replayview_drawitem); + draw_menu_list(m, 50, 100, replayview_drawitem, SCREEN_H); if(ctx->submenu) { ctx->submenu->draw(ctx->submenu); diff --git a/src/menu/spellpractice.c b/src/menu/spellpractice.c index f3cf1d3a..048d35f8 100644 --- a/src/menu/spellpractice.c +++ b/src/menu/spellpractice.c @@ -12,11 +12,12 @@ #include "common.h" #include "options.h" #include "global.h" +#include "video.h" static void draw_spell_menu(MenuData *m) { draw_options_menu_bg(m); draw_menu_title(m, "Spell Practice"); - draw_menu_list(m, 100, 100, NULL); + draw_menu_list(m, 100, 100, NULL, SCREEN_H); } MenuData* create_spell_menu(void) { diff --git a/src/menu/stagepractice.c b/src/menu/stagepractice.c index 29b2b59d..ce64ccfe 100644 --- a/src/menu/stagepractice.c +++ b/src/menu/stagepractice.c @@ -12,11 +12,12 @@ #include "common.h" #include "options.h" #include "global.h" +#include "video.h" static void draw_stgpract_menu(MenuData *m) { draw_options_menu_bg(m); draw_menu_title(m, "Stage Practice"); - draw_menu_list(m, 100, 100, NULL); + draw_menu_list(m, 100, 100, NULL, SCREEN_H); } MenuData* create_stgpract_menu(Difficulty diff) { diff --git a/src/menu/stageselect.c b/src/menu/stageselect.c index d4850534..1934e964 100644 --- a/src/menu/stageselect.c +++ b/src/menu/stageselect.c @@ -14,11 +14,12 @@ #include "stage.h" #include "stageselect.h" #include "common.h" +#include "video.h" static void draw_stage_menu(MenuData *m) { draw_options_menu_bg(m); draw_menu_title(m, "Select Stage"); - draw_menu_list(m, 100, 100, NULL); + draw_menu_list(m, 100, 100, NULL, SCREEN_H); } MenuData* create_stage_menu(void) { diff --git a/src/menu/submenus.c b/src/menu/submenus.c index 210cfae0..1bc601ce 100644 --- a/src/menu/submenus.c +++ b/src/menu/submenus.c @@ -15,6 +15,7 @@ #include "spellpractice.h" #include "stagepractice.h" #include "difficultyselect.h" +#include "musicroom.h" #include "global.h" #include "submenus.h" @@ -56,3 +57,7 @@ static void stgpract_do_choose_stage(CallChainResult ccr) { enter_menu(create_stgpract_menu(progress.game_settings.difficulty), NO_CALLCHAIN); } } + +void menu_action_enter_musicroom(MenuData *menu, void *arg) { + enter_menu(create_musicroom_menu(), NO_CALLCHAIN); +} diff --git a/src/menu/submenus.h b/src/menu/submenus.h index 25a75062..75c5b1fb 100644 --- a/src/menu/submenus.h +++ b/src/menu/submenus.h @@ -16,5 +16,6 @@ void menu_action_enter_stagemenu(MenuData *menu, void *arg); void menu_action_enter_replayview(MenuData *menu, void *arg); void menu_action_enter_spellpractice(MenuData *menu, void *arg); void menu_action_enter_stagepractice(MenuData *menu, void *arg); +void menu_action_enter_musicroom(MenuData *menu, void *arg); #endif // IGUARD_menu_submenus_h diff --git a/src/progress.c b/src/progress.c index 61890346..6b9f2a04 100644 --- a/src/progress.c +++ b/src/progress.c @@ -54,6 +54,10 @@ - PCMD_GAME_VERSION Sets the game version this file was last written with + + - PCMD_UNLOCK_BGMS + Unlocks BGMs in the music room + */ /* @@ -247,6 +251,12 @@ static void progress_read(SDL_RWops *file) { } break; + case PCMD_UNLOCK_BGMS: + if(progress_read_verify_cmd_size(vfile, cmd, cmdsize, sizeof(uint64_t))) { + progress.unlocked_bgms |= SDL_ReadLE64(vfile); + } + break; + default: log_warn("Unknown command %x (%u bytes). Will preserve as-is and not interpret", cmd, cmdsize); @@ -565,6 +575,20 @@ static void progress_write_cmd_game_version(SDL_RWops *vfile, void **arg) { taisei_version_write(vfile, &v); } +// +// PCMD_UNLOCK_BGMS +// + +static void progress_prepare_cmd_unlock_bgms(size_t *bufsize, void **arg) { + *bufsize += CMD_HEADER_SIZE + sizeof(uint64_t); +} + +static void progress_write_cmd_unlock_bgms(SDL_RWops *vfile, void **arg) { + SDL_WriteU8(vfile, PCMD_UNLOCK_BGMS); + SDL_WriteLE16(vfile, sizeof(uint64_t)); + SDL_WriteLE64(vfile, progress.unlocked_bgms); +} + // // Copy unhandled commands from the original file // @@ -610,6 +634,7 @@ static void progress_write(SDL_RWops *file) { {progress_prepare_cmd_endings, progress_write_cmd_endings, NULL}, {progress_prepare_cmd_game_settings, progress_write_cmd_game_settings, NULL}, // {progress_prepare_cmd_test, progress_write_cmd_test, NULL}, + {progress_prepare_cmd_unlock_bgms, progress_write_cmd_unlock_bgms, NULL}, {progress_prepare_cmd_unknown, progress_write_cmd_unknown, NULL}, {NULL} }; @@ -645,7 +670,7 @@ static void progress_write(SDL_RWops *file) { SDL_RWwrite(file, &cs, 4, 1); if(!SDL_RWwrite(file, buf, bufsize, 1)) { - log_fatal("SDL_RWwrite() failed: %s", SDL_GetError()); + log_error("SDL_RWwrite() failed: %s", SDL_GetError()); return; } @@ -653,6 +678,8 @@ static void progress_write(SDL_RWops *file) { SDL_RWclose(vfile); } +#define PROGRESS_UNLOCK_ALL + #ifdef PROGRESS_UNLOCK_ALL static void progress_unlock_all(void) { StageInfo *stg; @@ -665,6 +692,8 @@ static void progress_unlock_all(void) { } } } + + progress.unlocked_bgms = UINT64_MAX; } #endif @@ -736,3 +765,47 @@ uint32_t progress_times_any_good_ending_achieved(void) { return x; } + +static ProgressBGMID progress_bgm_id(const char *bgm) { + static const char* map[] = { + [PBGM_MENU] = "menu", + [PBGM_STAGE1] = "stage1", + [PBGM_STAGE1_BOSS] = "stage1boss", + [PBGM_STAGE2] = "stage2", + [PBGM_STAGE2_BOSS] = "stage2boss", + [PBGM_STAGE3] = "stage3", + [PBGM_STAGE3_BOSS] = "stage3boss", + [PBGM_STAGE4] = "stage4", + [PBGM_STAGE4_BOSS] = "stage4boss", + [PBGM_STAGE5] = "stage5", + [PBGM_STAGE5_BOSS] = "stage5boss", + [PBGM_STAGE6] = "stage6", + [PBGM_STAGE6_BOSS1] = "stage6boss_phase1", + [PBGM_STAGE6_BOSS2] = "stage6boss_phase2", + [PBGM_STAGE6_BOSS3] = "stage6boss_phase3", + [PBGM_ENDING] = "ending", + [PBGM_CREDITS] = "credits", + [PBGM_BONUS0] = "bonus0", + [PBGM_BONUS1] = "scuttle", + }; + + for(int i = 0; i < ARRAY_SIZE(map); ++i) { + if(!strcmp(map[i], bgm)) { + return i; + } + } + + UNREACHABLE; +} + +static inline uint64_t progress_bgm_bit(ProgressBGMID id) { + return (UINT64_C(1) << id); +} + +bool progress_is_bgm_unlocked(const char *name) { + return progress.unlocked_bgms & progress_bgm_bit(progress_bgm_id(name)); +} + +void progress_unlock_bgm(const char *name) { + progress.unlocked_bgms |= progress_bgm_bit(progress_bgm_id(name)); +} diff --git a/src/progress.h b/src/progress.h index d1431b53..59399db4 100644 --- a/src/progress.h +++ b/src/progress.h @@ -22,7 +22,7 @@ #endif typedef enum ProgfileCommand { -// do not reorder this! + // Do not reorder this! PCMD_UNLOCK_STAGES = 0x00, PCMD_UNLOCK_STAGES_WITH_DIFFICULTY = 0x01, @@ -31,23 +31,48 @@ typedef enum ProgfileCommand { PCMD_ENDINGS = 0x04, PCMD_GAME_SETTINGS = 0x05, PCMD_GAME_VERSION = 0x06, + PCMD_UNLOCK_BGMS = 0x07, } ProgfileCommand; typedef struct StageProgress { - // keep this struct small if you can - // see stage_get_progress_from_info() in stage.c for more information - - uint unlocked : 1; + // Keep this struct small if you can + // See stage_get_progress_from_info() in stage.c for more information uint32_t num_played; uint32_t num_cleared; + uchar unlocked : 1; } StageProgress; +typedef enum ProgressBGMID { + // Do not reorder! Append at the end only, reserve space if removing. + + PBGM_MENU, + PBGM_STAGE1, + PBGM_STAGE1_BOSS, + PBGM_STAGE2, + PBGM_STAGE2_BOSS, + PBGM_STAGE3, + PBGM_STAGE3_BOSS, + PBGM_STAGE4, + PBGM_STAGE4_BOSS, + PBGM_STAGE5, + PBGM_STAGE5_BOSS, + PBGM_STAGE6, + PBGM_STAGE6_BOSS1, + PBGM_STAGE6_BOSS2, + PBGM_STAGE6_BOSS3, + PBGM_ENDING, + PBGM_CREDITS, + PBGM_BONUS0, // old Iku theme + PBGM_BONUS1, // Scuttle theme +} ProgressBGMID; + struct UnknownCmd; typedef struct GlobalProgress { uint32_t hiscore; uint32_t achieved_endings[NUM_ENDINGS]; + uint64_t unlocked_bgms; struct UnknownCmd *unknown; struct { @@ -66,4 +91,7 @@ void progress_unload(void); uint32_t progress_times_any_ending_achieved(void); uint32_t progress_times_any_good_ending_achieved(void); +bool progress_is_bgm_unlocked(const char *name); +void progress_unlock_bgm(const char *name); + #endif // IGUARD_progress_h diff --git a/src/resource/bgm.c b/src/resource/bgm.c index 1121ad0d..86ba39ff 100644 --- a/src/resource/bgm.c +++ b/src/resource/bgm.c @@ -14,7 +14,7 @@ #include "sfxbgm_common.h" #include "util.h" -static char* bgm_path(const char *name) { +static char *bgm_path(const char *name) { return sfxbgm_make_path(BGM_PATH_PREFIX, name, true); } @@ -32,37 +32,25 @@ static MusicImpl *load_music(const char *path) { static void *load_bgm_begin(const char *path, uint flags) { Music *mus = calloc(1, sizeof(Music)); - double loop_point = 0; if(strendswith(path, ".bgm")) { - char *intro = NULL; - char *loop = NULL; + char *basename = resource_util_basename(BGM_PATH_PREFIX, path); + mus->meta = get_resource_data(RES_BGM_METADATA, basename, flags); + free(basename); - if(!parse_keyvalue_file_with_spec(path, (KVSpec[]) { - { "artist" }, // don’t print a warning because this field is supposed to be here - { "title", .out_str = &mus->title }, - { "loop", .out_str = &loop }, - { "loop_point", .out_double = &loop_point }, - { NULL } - })) { - log_error("Failed to parse bgm config '%s'", path); - } else { - mus->impl = load_music(loop); + if(mus->meta) { + mus->impl = load_music(mus->meta->loop_path); } - - free(intro); - free(loop); } else { mus->impl = load_music(path); } if(!mus->impl) { - free(mus->title); free(mus); mus = NULL; log_error("Failed to load bgm '%s'", path); - } else if(loop_point > 0) { - _a_backend.funcs.music_set_loop_point(mus->impl, loop_point); + } else if(mus->meta->loop_point > 0) { + _a_backend.funcs.music_set_loop_point(mus->impl, mus->meta->loop_point); } return mus; @@ -75,7 +63,6 @@ static void *load_bgm_end(void *opaque, const char *path, uint flags) { static void unload_bgm(void *vmus) { Music *mus = vmus; _a_backend.funcs.music_unload(mus->impl); - free(mus->title); free(mus); } diff --git a/src/resource/bgm_metadata.c b/src/resource/bgm_metadata.c new file mode 100644 index 00000000..fec55813 --- /dev/null +++ b/src/resource/bgm_metadata.c @@ -0,0 +1,75 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" + +#include "bgm_metadata.h" +#include "bgm.h" +#include "util.h" + +static char *bgm_meta_path(const char *name) { + return strjoin(BGM_PATH_PREFIX, name, ".bgm", NULL); +} + +static bool check_bgm_meta_path(const char *path) { + return strendswith(path, ".bgm") && strstartswith(path, BGM_PATH_PREFIX); +} + +static void free_metadata_fields(MusicMetadata *meta) { + free(meta->artist); + free(meta->comment); + free(meta->loop_path); + free(meta->title); +} + +static void *load_bgm_meta_begin(const char *path, uint flags) { + MusicMetadata meta = { 0 }; + + if(!parse_keyvalue_file_with_spec(path, (KVSpec[]) { + { "artist", .out_str = &meta.artist }, + { "title", .out_str = &meta.title }, + { "comment", .out_str = &meta.comment }, + { "loop", .out_str = &meta.loop_path }, + { "loop_point", .out_double = &meta.loop_point }, + { NULL } + })) { + log_error("Failed to parse BGM metadata '%s'", path); + free_metadata_fields(&meta); + return NULL; + } + + if(meta.comment) { + expand_escape_sequences(meta.comment); + } + + return memdup(&meta, sizeof(meta)); +} + +static void *load_bgm_meta_end(void *opaque, const char *path, uint flags) { + return opaque; +} + +static void unload_bgm_meta(void *vmus) { + MusicMetadata *meta = vmus; + free_metadata_fields(meta); + free(meta); +} + +ResourceHandler bgm_metadata_res_handler = { + .type = RES_BGM_METADATA, + .typename = "bgm metadata", + .subdir = BGM_PATH_PREFIX, + + .procs = { + .find = bgm_meta_path, + .check = check_bgm_meta_path, + .begin_load = load_bgm_meta_begin, + .end_load = load_bgm_meta_end, + .unload = unload_bgm_meta, + }, +}; diff --git a/src/resource/bgm_metadata.h b/src/resource/bgm_metadata.h new file mode 100644 index 00000000..2903765c --- /dev/null +++ b/src/resource/bgm_metadata.h @@ -0,0 +1,26 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#ifndef IGUARD_resource_bgm_metadata_h +#define IGUARD_resource_bgm_metadata_h + +#include "taisei.h" + +#include "resource.h" + +typedef struct MusicMetadata { + char *title; + char *artist; + char *comment; + char *loop_path; + double loop_point; +} MusicMetadata; + +extern ResourceHandler bgm_metadata_res_handler; + +#endif // IGUARD_resource_bgm_metadata_h diff --git a/src/resource/font.c b/src/resource/font.c index 03196e17..971f3237 100644 --- a/src/resource/font.c +++ b/src/resource/font.c @@ -1261,7 +1261,7 @@ void text_wrap(Font *font, const char *src, double width, char *buf, size_t bufs strcpy(src_copy, src); *buf = 0; - while((next = strtok_r(NULL, " \t\n", &sptr))) { + while((next = strtok_r(NULL, " \t", &sptr))) { int curwidth; if(!*next) { diff --git a/src/resource/meson.build b/src/resource/meson.build index 5866d7e4..8a7db6ef 100644 --- a/src/resource/meson.build +++ b/src/resource/meson.build @@ -2,6 +2,7 @@ resource_src = files( 'animation.c', 'bgm.c', + 'bgm_metadata.c', 'font.c', 'model.c', 'postprocess.c', diff --git a/src/resource/resource.c b/src/resource/resource.c index 5c89e563..9988521a 100644 --- a/src/resource/resource.c +++ b/src/resource/resource.c @@ -19,6 +19,7 @@ #include "animation.h" #include "sfx.h" #include "bgm.h" +#include "bgm_metadata.h" #include "shader_object.h" #include "shader_program.h" #include "model.h" @@ -33,6 +34,7 @@ ResourceHandler *_handlers[] = { [RES_ANIM] = &animation_res_handler, [RES_SFX] = &sfx_res_handler, [RES_BGM] = &bgm_res_handler, + [RES_BGM_METADATA] = &bgm_metadata_res_handler, [RES_MODEL] = &model_res_handler, [RES_POSTPROCESS] = &postprocess_res_handler, [RES_SPRITE] = &sprite_res_handler, diff --git a/src/resource/resource.h b/src/resource/resource.h index ba805e5d..d16fbd88 100644 --- a/src/resource/resource.h +++ b/src/resource/resource.h @@ -18,6 +18,7 @@ typedef enum ResourceType { RES_ANIM, RES_SFX, RES_BGM, + RES_BGM_METADATA, RES_SHADER_OBJECT, RES_SHADER_PROGRAM, RES_MODEL, diff --git a/src/stage.c b/src/stage.c index 1e1a892a..a09d4764 100644 --- a/src/stage.c +++ b/src/stage.c @@ -885,3 +885,9 @@ void stage_end_loop(void* ctx) { run_call_chain(&s->cc, NULL); free(s); } + +void stage_unlock_bgm(const char *bgm) { + if(global.replaymode != REPLAY_PLAY && !global.plr.continues_used) { + progress_unlock_bgm(bgm); + } +} diff --git a/src/stage.h b/src/stage.h index 6f81bf20..095b392f 100644 --- a/src/stage.h +++ b/src/stage.h @@ -145,6 +145,8 @@ void stage_set_voltage_thresholds(uint easy, uint normal, uint hard, uint lunati bool stage_is_cleared(void); +void stage_unlock_bgm(const char *bgm); + #include "stages/stage1.h" #include "stages/stage2.h" #include "stages/stage3.h" diff --git a/src/stages/stage1_events.c b/src/stages/stage1_events.c index cb4280a9..5962cdb7 100644 --- a/src/stages/stage1_events.c +++ b/src/stages/stage1_events.c @@ -1236,10 +1236,12 @@ void stage1_events(void) { AT(5000) { enemy_kill_all(&global.enemies); + stage_unlock_bgm("stage1"); global.boss = create_cirno(); } AT(5100) { + stage_unlock_bgm("stage1boss"); global.dialog = stage1_dialog_post_boss(); } diff --git a/src/stages/stage2_events.c b/src/stages/stage2_events.c index c85f3984..dd21ca03 100644 --- a/src/stages/stage2_events.c +++ b/src/stages/stage2_events.c @@ -839,10 +839,12 @@ void stage2_events(void) { create_enemy1c(VIEWPORT_W*(0.5+0.1*sqrt(_i)*(1-2*(_i&1)))-10.0*I, 2000, BigFairy, stage2_accel_circle, 2.0*I); AT(5100) { + stage_unlock_bgm("stage2"); global.boss = create_hina(); } AT(5180) { + stage_unlock_bgm("stage2boss"); global.dialog = stage2_dialog_post_boss(); } diff --git a/src/stages/stage3.c b/src/stages/stage3.c index f6597c28..3701d9bb 100644 --- a/src/stages/stage3.c +++ b/src/stages/stage3.c @@ -361,6 +361,7 @@ static void stage3_spellpractice_start(void) { if(global.stage->spell->draw_rule == scuttle_spellbg) { skip_background_anim(stage3_update, 2800, &global.timer, NULL); global.boss = stage3_spawn_scuttle(BOSS_DEFAULT_SPAWN_POS); + stage_unlock_bgm("scuttle"); stage_start_bgm("scuttle"); } else { skip_background_anim(stage3_update, 5300 + STAGE3_MIDBOSS_TIME, &global.timer, NULL); diff --git a/src/stages/stage3_events.c b/src/stages/stage3_events.c index 2a4ebc50..4f152402 100644 --- a/src/stages/stage3_events.c +++ b/src/stages/stage3_events.c @@ -1561,10 +1561,12 @@ void stage3_events(void) { } AT(5300 + midboss_time) { + stage_unlock_bgm("stage3"); global.boss = stage3_create_boss(); } AT(5400 + midboss_time) { + stage_unlock_bgm("stage3boss"); global.dialog = stage3_dialog_post_boss(); } diff --git a/src/stages/stage4_events.c b/src/stages/stage4_events.c index bb0b6bff..1a1a9da8 100644 --- a/src/stages/stage4_events.c +++ b/src/stages/stage4_events.c @@ -1496,11 +1496,15 @@ void stage4_events(void) { FROM_TO(4800 + midboss_time, 5200 + midboss_time, 10) create_enemy1c(20.0*I+I*VIEWPORT_H/3*frand()+VIEWPORT_W*(_i&1), 100, Swirl, stage4_explosive, (1-2*(_i&1))*3+I); - AT(5300 + midboss_time) + AT(5300 + midboss_time) { + stage_unlock_bgm("stage4"); global.boss = create_kurumi(); + } - AT(5400 + midboss_time) + AT(5400 + midboss_time) { + stage_unlock_bgm("stage4boss"); global.dialog = stage4_dialog_post_boss(); + } AT(5405 + midboss_time) { stage_finish(GAMEOVER_SCORESCREEN); diff --git a/src/stages/stage5_events.c b/src/stages/stage5_events.c index c1849e47..39630473 100644 --- a/src/stages/stage5_events.c +++ b/src/stages/stage5_events.c @@ -1308,10 +1308,12 @@ void stage5_events(void) { } AT(6960) { + stage_unlock_bgm("stage5"); global.boss = create_iku(); } AT(6980) { + stage_unlock_bgm("stage5boss"); global.dialog = stage5_dialog_post_boss(); } diff --git a/src/stages/stage6_events.c b/src/stages/stage6_events.c index f467cb49..93d764f2 100644 --- a/src/stages/stage6_events.c +++ b/src/stages/stage6_events.c @@ -923,6 +923,7 @@ static void elly_paradigm_shift(Boss *b, int t) { AT(100) { if(global.stage->type != STAGE_SPELL) { + stage_unlock_bgm("stage6boss_phase1"); stage_start_bgm("stage6boss_phase2"); stagetext_add("Paradigm Shift!", VIEWPORT_W/2+I*(VIEWPORT_H/2+64), ALIGN_CENTER, get_font("big"), RGB(1, 1, 1), 0, 120, 10, 30); } @@ -2837,6 +2838,7 @@ static void elly_begin_toe(Boss *b, int t) { AT(1) { start_fall_over(); + stage_unlock_bgm("stage6boss_phase2"); stage_start_bgm("stage6boss_phase3"); global.shake_view_fade = 0; } @@ -2902,10 +2904,13 @@ void stage6_events(void) { AT(2300) create_enemy3c(200.0*I-200, ENEMY_IMMUNE, Scythe, scythe_mid, 1, 0.2*I, 1); - AT(3800) + AT(3800) { + stage_unlock_bgm("stage6"); global.boss = create_elly(); + } AT(3805) { + stage_unlock_bgm("stage6boss_phase3"); stage_finish(GAMEOVER_SCORESCREEN); } } diff --git a/src/util/kvparser.c b/src/util/kvparser.c index 41ffb3c2..ab998423 100644 --- a/src/util/kvparser.c +++ b/src/util/kvparser.c @@ -17,11 +17,12 @@ bool parse_keyvalue_stream_cb(SDL_RWops *strm, KVCallback callback, void *data) { static const char separator[] = "= "; - char buffer[256]; + size_t bufsize = 256; + char *buffer = malloc(bufsize); int lineno = 0; int errors = 0; - loopstart: while(SDL_RWgets(strm, buffer, sizeof(buffer))) { + loopstart: while(SDL_RWgets_realloc(strm, &buffer, &bufsize)) { char *ptr = buffer; char *sep, *key, *val; @@ -68,6 +69,7 @@ bool parse_keyvalue_stream_cb(SDL_RWops *strm, KVCallback callback, void *data) } } + free(buffer); return !errors; } diff --git a/src/util/miscmath.c b/src/util/miscmath.c index bfab86d7..5581dce2 100644 --- a/src/util/miscmath.c +++ b/src/util/miscmath.c @@ -154,6 +154,11 @@ double smoothstep(double edge0, double edge1, double x) { return x * x * (3 - 2 * x); } +double smoothmin(double a, double b, double k) { + float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); + return lerp(b, a, h) - k * h * (1.0 - h); +} + int sign(double x) { return (x > 0) - (x < 0); } diff --git a/src/util/miscmath.h b/src/util/miscmath.h index 9e3b58f1..fd9c009a 100644 --- a/src/util/miscmath.h +++ b/src/util/miscmath.h @@ -13,6 +13,7 @@ #define DEG2RAD (M_PI/180.0) #define RAD2DEG (180.0/M_PI) +#define GOLDEN_RATIO 1.618033988749895 double lerp(double v0, double v1, double f) attr_const; complex clerp(complex v0, complex v1, double f) attr_const; @@ -25,6 +26,7 @@ double min(double, double) attr_const; double max(double, double) attr_const; double clamp(double, double, double) attr_const; double smoothstep(double edge0, double edge1, double x) attr_const; +double smoothmin(double a, double b, double k); double approach(double v, double t, double d) attr_const; float fapproach(float v, float t, float d) attr_const; void approach_p(double *v, double t, double d); diff --git a/src/util/stringops.c b/src/util/stringops.c index 3c4d6c74..8d451d13 100644 --- a/src/util/stringops.c +++ b/src/util/stringops.c @@ -473,3 +473,29 @@ void hexdigest(uint8_t *input, size_t input_size, char *output, size_t output_si *output = 0; } + +void expand_escape_sequences(char *str) { + bool in_escape = false; + char *p = str; + + for(p = str; *p; ++p) { + if(in_escape) { + switch(*p) { + case 'n': p[-1] = '\n'; break; + case 't': p[-1] = '\t'; break; + case 'r': p[-1] = '\r'; break; + default: p[-1] = *p; break; + } + + memmove(p, p + 1, strlen(p + 1) + 1); + --p; + in_escape = false; + } else if(*p == '\\') { + in_escape = true; + } + } + + if(in_escape) { + p[-1] = 0; + } +} diff --git a/src/util/stringops.h b/src/util/stringops.h index 1e86c745..df5f4f98 100644 --- a/src/util/stringops.h +++ b/src/util/stringops.h @@ -49,6 +49,7 @@ void strip_trailing_slashes(char *buf); char* strtok_r(char *str, const char *delim, char **nextp); char* strappend(char **dst, char *src); char* strftimealloc(const char *fmt, const struct tm *timeinfo); +void expand_escape_sequences(char *str); uint32_t* ucs4chr(const uint32_t *ucs4, uint32_t chr); size_t ucs4len(const uint32_t *ucs4); diff --git a/src/vfs/setup_emscripten.c b/src/vfs/setup_emscripten.c index 7525cf88..80dcfb78 100644 --- a/src/vfs/setup_emscripten.c +++ b/src/vfs/setup_emscripten.c @@ -1,3 +1,10 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ #include "taisei.h" diff --git a/src/vfs/sync_emscripten.c b/src/vfs/sync_emscripten.c index 735a5065..682f5121 100644 --- a/src/vfs/sync_emscripten.c +++ b/src/vfs/sync_emscripten.c @@ -1,3 +1,12 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" #include "public.h" #include "util.h" diff --git a/src/vfs/sync_noop.c b/src/vfs/sync_noop.c index c0366a66..ef570522 100644 --- a/src/vfs/sync_noop.c +++ b/src/vfs/sync_noop.c @@ -1,3 +1,12 @@ +/* + * This software is licensed under the terms of the MIT-License + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#include "taisei.h" #include "public.h"