feat: Add Firebase high score sync, menu music, and gameplay improvements
- Integrate Firebase Realtime Database for high score synchronization - Add cpr and nlohmann-json dependencies for HTTP requests - Implement async score loading from Firebase with local fallback - Submit all scores > 0 to Firebase in background thread - Always prompt for player name on game over if score > 0 - Add dedicated menu music system - Implement menu track support in Audio class with looping - Add "Every Block You Take.mp3" as main menu theme - Automatically switch between menu and game music on state transitions - Load menu track asynchronously to prevent startup delays - Update level speed progression to match web version - Replace NES frame-based gravity with explicit millisecond values - Implement 20-level speed table (1000ms to 60ms) - Ensure consistent gameplay between C++ and web versions - Fix startup performance issues - Move score loading to background thread to prevent UI freeze - Optimize Firebase network requests with 2s timeout - Add graceful fallback to local scores on network failure Files modified: - src/persistence/Scores.cpp/h - Firebase integration - src/audio/Audio.cpp/h - Menu music support - src/core/GravityManager.cpp/h - Level speed updates - src/main.cpp - State-based music switching, async loading - CMakeLists.txt - Add cpr and nlohmann-json dependencies - vcpkg.json - Update dependency list
This commit is contained in:
@ -104,25 +104,52 @@ void Audio::feed(Uint32 bytesWanted, SDL_AudioStream* stream){
|
||||
std::vector<int16_t> mix(outSamples, 0);
|
||||
|
||||
// 1) Mix music into buffer (if not muted)
|
||||
if(!muted && current >= 0){
|
||||
// 1) Mix music into buffer (if not muted)
|
||||
if(!muted && playing){
|
||||
size_t cursorBytes = 0;
|
||||
while(cursorBytes < bytesWanted){
|
||||
if(current < 0) break;
|
||||
auto &trk = tracks[current];
|
||||
size_t samplesAvail = trk.pcm.size() - trk.cursor; // samples (int16)
|
||||
if(samplesAvail == 0){ nextTrack(); if(current < 0) break; continue; }
|
||||
AudioTrack* trk = nullptr;
|
||||
|
||||
if (isMenuMusic) {
|
||||
if (menuTrack.ok) trk = &menuTrack;
|
||||
} else {
|
||||
if (current >= 0 && current < (int)tracks.size()) trk = &tracks[current];
|
||||
}
|
||||
|
||||
if (!trk) break;
|
||||
|
||||
size_t samplesAvail = trk->pcm.size() - trk->cursor; // samples (int16)
|
||||
if(samplesAvail == 0){
|
||||
if (isMenuMusic) {
|
||||
trk->cursor = 0; // Loop menu music
|
||||
continue;
|
||||
} else {
|
||||
nextTrack();
|
||||
if(current < 0) break;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
size_t samplesNeeded = (bytesWanted - cursorBytes) / sizeof(int16_t);
|
||||
size_t toCopy = (samplesAvail < samplesNeeded) ? samplesAvail : samplesNeeded;
|
||||
if(toCopy == 0) break;
|
||||
|
||||
// Mix add with clamp
|
||||
size_t startSample = cursorBytes / sizeof(int16_t);
|
||||
for(size_t i=0;i<toCopy;++i){
|
||||
int v = (int)mix[startSample+i] + (int)trk.pcm[trk.cursor+i];
|
||||
int v = (int)mix[startSample+i] + (int)trk->pcm[trk->cursor+i];
|
||||
if(v>32767) v=32767; if(v<-32768) v=-32768; mix[startSample+i] = (int16_t)v;
|
||||
}
|
||||
trk.cursor += toCopy;
|
||||
trk->cursor += toCopy;
|
||||
cursorBytes += (Uint32)(toCopy * sizeof(int16_t));
|
||||
if(trk.cursor >= trk.pcm.size()) nextTrack();
|
||||
|
||||
if(trk->cursor >= trk->pcm.size()) {
|
||||
if (isMenuMusic) {
|
||||
trk->cursor = 0; // Loop menu music
|
||||
} else {
|
||||
nextTrack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,6 +293,39 @@ int Audio::getLoadedTrackCount() const {
|
||||
return loadedCount;
|
||||
}
|
||||
|
||||
void Audio::setMenuTrack(const std::string& path) {
|
||||
menuTrack.path = path;
|
||||
#ifdef _WIN32
|
||||
// Ensure MF is started (might be redundant if init called, but safe)
|
||||
if(!mfStarted){ if(FAILED(MFStartup(MF_VERSION))) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MFStartup failed"); } else mfStarted=true; }
|
||||
|
||||
if (decodeMP3(path, menuTrack.pcm, menuTrack.rate, menuTrack.channels)) {
|
||||
menuTrack.ok = true;
|
||||
} else {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to decode menu track %s", path.c_str());
|
||||
}
|
||||
#else
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MP3 unsupported (stub): %s", path.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void Audio::playMenuMusic() {
|
||||
isMenuMusic = true;
|
||||
if (menuTrack.ok) {
|
||||
menuTrack.cursor = 0;
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
void Audio::playGameMusic() {
|
||||
isMenuMusic = false;
|
||||
// If we were playing menu music, we might want to pick a random track or resume
|
||||
if (current < 0 && !tracks.empty()) {
|
||||
nextTrack();
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
void Audio::shutdown(){
|
||||
// Stop background loading thread first
|
||||
if (loadingThread.joinable()) {
|
||||
|
||||
Reference in New Issue
Block a user