Add a basic music room

This commit is contained in:
Andrei Alexeyev 2019-03-11 01:21:43 +02:00
parent b314cbd7d7
commit a63e8f4a69
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
62 changed files with 572 additions and 69 deletions

View file

@ -1,4 +1,5 @@
title = Divine Correction for Outliers
artist = Tuck V
loop = res/bgm/bonus0.ogg
loop_point = 2.909093
loop_point = 2.909093
comment = The decision to arrange a characters 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 cant decide if I did the original justice with this one or not, but I know it holds a special place in my heart.

View file

@ -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 thats a lot gentler than a thunderstorm… perhaps a bit more like a rainbow.

View file

@ -1,3 +1,4 @@
title = Dream Cycle
artist = Tuck V
loop = res/bgm/ending.ogg
comment = The endings 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 its about time the protagonists have had a snack.

View file

@ -1,4 +1,5 @@
title = Delta Concision
artist = Tuck V
loop = res/bgm/menu.ogg
loop_point = 13.71
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.

View file

@ -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 Scuttles 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.

View file

@ -1,4 +1,5 @@
title = The Fog Invites Unseen Mischief
artist = Tuck V
loop = res/bgm/stage1/stage.ogg
loop_point = 6.857143
loop_point = 6.857143
comment = The first stages 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.

View file

@ -1,4 +1,5 @@
title = An Impish Reflection on the Water
artist = Tuck V
loop = res/bgm/stage1/boss.ogg
loop_point = 3.200000
loop_point = 3.200000
comment = Cirnos theme.\n\nShes feeling just as brave this time around, and acting a fair bit stronger.\n\nHowever, shes really just a minor annoyance, cold and brittle. The protagonists brush off her pranks and move on.

View file

@ -1,4 +1,5 @@
title = Treasure the Wager, Treasure the Odds
artist = Tuck V
loop = res/bgm/stage2/stage.ogg
loop_point = 10.124989
loop_point = 10.124989
comment = The second stages 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.

View file

@ -1,3 +1,4 @@
title = The Cheerful Presence of a Dark God
artist = Tuck V
loop = res/bgm/stage2/boss.ogg
loop = res/bgm/stage2/boss.ogg
comment = Hina Kagiyamas theme.\n\nThis time, its all up to chance.\n\nIts a little merciful sometimes, but you still have to do your best, no matter what. Gambling might be a poor stress-reliever…

View file

@ -1,3 +1,4 @@
title = Fizeau's Finding - Lightray Vector
title = Fizeaus Finding - Lightray Vector
artist = Tuck V
loop = res/bgm/stage3/stage.ogg
loop = res/bgm/stage3/stage.ogg
comment = The third stages theme.\n\nIts 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.

View file

@ -1,4 +1,5 @@
title = Lightningbug ~ Lightning Heart
artist = Tuck V
loop = res/bgm/stage3/boss.ogg
loop_point = 1.500000
loop_point = 1.500000
comment = Wriggle Nightbugs theme.\n\nA very motivated bug. Shes at the top of the world! — No, someone else is, shes 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…

View file

@ -1,4 +1,5 @@
title = Evidence of Ki Domestication
artist = Tuck V
loop = res/bgm/stage4/stage.ogg
loop_point = 32.470590
loop_point = 32.470590
comment = The fourth stages 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.

View file

@ -1,4 +1,5 @@
title = Marginal Red ~ The Red Word
artist = Tuck V
loop = res/bgm/stage4/boss.ogg
loop_point = 24.000000
loop_point = 24.000000
comment = Kurumis theme.\n\nOld faces and new dreams. Shes 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 theyre closing in.

View file

@ -1,3 +1,4 @@
title = Speak Not Falsely ~ Excitation Field
artist = Tuck V
loop = res/bgm/stage5/stage.ogg
loop = res/bgm/stage5/stage.ogg
comment = The fifth stages 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 dont make a single dishonest step.

View file

@ -1,4 +1,5 @@
title = A Prayer Worth Three Hundred Coulombs
artist = Tuck V
loop = res/bgm/stage5/boss.ogg
loop_point = 12.80
loop_point = 12.80
comment = Iku Nagaes theme.\n\nThis song went through the wringer. Perhaps thats 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.

View file

@ -2,3 +2,4 @@ title = Summit of Revelations
artist = Tuck V
loop = res/bgm/stage6/stage.ogg
loop_point = 13.71
comment = The sixth stages 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.

View file

@ -1,3 +1,4 @@
title = Cosmological Battle ~ The Vacuum Catastrophe
artist = Tuck V
loop = res/bgm/stage6/boss_phase1.ogg
comment = Ellys 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 apples fallen on her head. Shes a guard of a different kind of gate now.

View file

@ -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 = Ellys 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 wouldnt speak the truth.

View file

@ -2,3 +2,4 @@ title = Immutable Truth
artist = Tuck V
loop = res/bgm/stage6/boss_phase3.ogg
loop_point = 35.64
comment = Ellys 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.

View file

@ -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");

View file

@ -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 {

View file

@ -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++;
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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");

View file

@ -7,6 +7,7 @@ menu_src = files(
'ingamemenu.c',
'mainmenu.c',
'menu.c',
'musicroom.c',
'options.c',
'replayview.c',
'savereplay.c',

166
src/menu/musicroom.c Normal file
View file

@ -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 <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
*/
#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;
}

18
src/menu/musicroom.h Normal file
View file

@ -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 <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
*/
#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

View file

@ -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);

View file

@ -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) {

View file

@ -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) {

View file

@ -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) {

View file

@ -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);
}

View file

@ -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

View file

@ -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));
}

View file

@ -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

View file

@ -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" }, // dont 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);
}

View file

@ -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 <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
*/
#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,
},
};

View file

@ -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 <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
*/
#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

View file

@ -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) {

View file

@ -2,6 +2,7 @@
resource_src = files(
'animation.c',
'bgm.c',
'bgm_metadata.c',
'font.c',
'model.c',
'postprocess.c',

View file

@ -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,

View file

@ -18,6 +18,7 @@ typedef enum ResourceType {
RES_ANIM,
RES_SFX,
RES_BGM,
RES_BGM_METADATA,
RES_SHADER_OBJECT,
RES_SHADER_PROGRAM,
RES_MODEL,

View file

@ -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);
}
}

View file

@ -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"

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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);

View file

@ -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();
}

View file

@ -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);

View file

@ -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();
}

View file

@ -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");