fixes for mac music
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,14 +1,14 @@
|
|||||||
@echo off
|
@echo off
|
||||||
echo Converting MP3 files to WAV using Windows Media Player...
|
echo Convert MP3 files to OGG (preferred) for cross-platform playback...
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
REM Check if we have access to Windows Media Format SDK
|
REM Check if we have access to Windows Media Format SDK
|
||||||
set MUSIC_DIR=assets\music
|
set MUSIC_DIR=assets\music
|
||||||
|
|
||||||
REM List of MP3 files to convert
|
REM List of MP3 files to convert
|
||||||
set FILES=amazing.mp3 boom_tetris.mp3 great_move.mp3 impressive.mp3 keep_that_ryhtm.mp3 lets_go.mp3 nice_combo.mp3 smooth_clear.mp3 triple_strike.mp3 well_played.mp3 wonderful.mp3 you_fire.mp3 you_re_unstoppable.mp3
|
set FILES=amazing.mp3 boom_tetris.mp3 great_move.mp3 impressive.mp3 keep_that_ryhtm.mp3 lets_go.mp3 nice_combo.mp3 smooth_clear.mp3 triple_strike.mp3 well_played.mp3 wonderful.mp3 you_fire.mp3 you_re_unstoppable.mp3 hard_drop_001.mp3 new_level.mp3
|
||||||
|
|
||||||
echo Please convert these MP3 files to WAV format manually:
|
echo Please convert these MP3 files to OGG Vorbis manually (or run convert_to_ogg.ps1 on Windows):
|
||||||
echo.
|
echo.
|
||||||
for %%f in (%FILES%) do (
|
for %%f in (%FILES%) do (
|
||||||
echo - %MUSIC_DIR%\%%f
|
echo - %MUSIC_DIR%\%%f
|
||||||
@@ -17,13 +17,12 @@ for %%f in (%FILES%) do (
|
|||||||
echo.
|
echo.
|
||||||
echo Recommended settings for conversion:
|
echo Recommended settings for conversion:
|
||||||
echo - Sample Rate: 44100 Hz
|
echo - Sample Rate: 44100 Hz
|
||||||
echo - Bit Depth: 16-bit
|
|
||||||
echo - Channels: Stereo (2)
|
echo - Channels: Stereo (2)
|
||||||
echo - Format: PCM WAV
|
echo - Use OGG Vorbis quality ~4 (or convert to FLAC if you prefer lossless)
|
||||||
echo.
|
echo.
|
||||||
echo You can use:
|
echo You can use:
|
||||||
echo - Audacity (free): https://www.audacityteam.org/
|
echo - Audacity (free): https://www.audacityteam.org/
|
||||||
echo - VLC Media Player (free): Media ^> Convert/Save
|
echo - VLC Media Player (free): Media ^> Convert/Save
|
||||||
echo - Any audio converter software
|
echo - ffmpeg (CLI): ffmpeg -i input.mp3 -c:a libvorbis -qscale:a 4 output.ogg
|
||||||
echo.
|
echo.
|
||||||
pause
|
pause
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Convert MP3 sound effects to OGG Vorbis format for cross-platform playback
|
||||||
|
# Requires ffmpeg (https://ffmpeg.org/). OGG keeps files small while SDL's decoders
|
||||||
|
# work everywhere the game ships.
|
||||||
|
|
||||||
|
$musicDir = "assets\music"
|
||||||
|
$sourceFiles = @(
|
||||||
|
"amazing.mp3",
|
||||||
|
"boom_tetris.mp3",
|
||||||
|
"great_move.mp3",
|
||||||
|
"impressive.mp3",
|
||||||
|
"keep_that_ryhtm.mp3",
|
||||||
|
"lets_go.mp3",
|
||||||
|
"nice_combo.mp3",
|
||||||
|
"smooth_clear.mp3",
|
||||||
|
"triple_strike.mp3",
|
||||||
|
"well_played.mp3",
|
||||||
|
"wonderful.mp3",
|
||||||
|
"you_fire.mp3",
|
||||||
|
"you_re_unstoppable.mp3",
|
||||||
|
"hard_drop_001.mp3",
|
||||||
|
"new_level.mp3",
|
||||||
|
"Every Block You Take.mp3"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!(Get-Command ffmpeg -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host "ffmpeg is required. Install via https://ffmpeg.org/ or winget install Gyan.FFmpeg" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Converting MP3 sound effects to OGG..." -ForegroundColor Cyan
|
||||||
|
foreach ($file in $sourceFiles) {
|
||||||
|
$src = Join-Path $musicDir $file
|
||||||
|
if (!(Test-Path $src)) {
|
||||||
|
Write-Host "Skipping (missing): $src" -ForegroundColor Yellow
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$ogg = ($file -replace ".mp3$", ".ogg")
|
||||||
|
$dest = Join-Path $musicDir $ogg
|
||||||
|
Write-Host "-> $ogg" -ForegroundColor Green
|
||||||
|
& ffmpeg -y -i $src -c:a libvorbis -qscale:a 4 $dest > $null 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "Failed to convert $file" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host "Conversion complete." -ForegroundColor Green
|
||||||
+9
-61
@@ -1,63 +1,11 @@
|
|||||||
# Convert MP3 sound effects to WAV format
|
# Deprecated shim: point developers to the OGG conversion workflow
|
||||||
# This script converts all MP3 sound effect files to WAV for better compatibility
|
Write-Host "convert_to_wav.ps1 is deprecated." -ForegroundColor Yellow
|
||||||
|
Write-Host "Use convert_to_ogg.ps1 for generating assets with OGG Vorbis." -ForegroundColor Yellow
|
||||||
|
|
||||||
$musicDir = "assets\music"
|
$oggScript = Join-Path $PSScriptRoot "convert_to_ogg.ps1"
|
||||||
$mp3Files = @(
|
if (Test-Path $oggScript) {
|
||||||
"amazing.mp3",
|
& $oggScript
|
||||||
"boom_tetris.mp3",
|
} else {
|
||||||
"great_move.mp3",
|
Write-Host "Missing convert_to_ogg.ps1" -ForegroundColor Red
|
||||||
"impressive.mp3",
|
exit 1
|
||||||
"keep_that_ryhtm.mp3",
|
|
||||||
"lets_go.mp3",
|
|
||||||
"nice_combo.mp3",
|
|
||||||
"smooth_clear.mp3",
|
|
||||||
"triple_strike.mp3",
|
|
||||||
"well_played.mp3",
|
|
||||||
"wonderful.mp3",
|
|
||||||
"you_fire.mp3",
|
|
||||||
"you_re_unstoppable.mp3"
|
|
||||||
)
|
|
||||||
|
|
||||||
Write-Host "Converting MP3 sound effects to WAV format..." -ForegroundColor Green
|
|
||||||
|
|
||||||
foreach ($mp3File in $mp3Files) {
|
|
||||||
$mp3Path = Join-Path $musicDir $mp3File
|
|
||||||
$wavFile = $mp3File -replace "\.mp3$", ".wav"
|
|
||||||
$wavPath = Join-Path $musicDir $wavFile
|
|
||||||
|
|
||||||
if (Test-Path $mp3Path) {
|
|
||||||
Write-Host "Converting $mp3File to $wavFile..." -ForegroundColor Yellow
|
|
||||||
|
|
||||||
# Try ffmpeg first (most common)
|
|
||||||
$ffmpegResult = $null
|
|
||||||
try {
|
|
||||||
$ffmpegResult = & ffmpeg -i $mp3Path -acodec pcm_s16le -ar 44100 -ac 2 $wavPath -y 2>&1
|
|
||||||
if ($LASTEXITCODE -eq 0) {
|
|
||||||
Write-Host "✓ Successfully converted $mp3File" -ForegroundColor Green
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
# FFmpeg not available, try other methods
|
|
||||||
}
|
|
||||||
|
|
||||||
# Try Windows Media Format SDK (if available)
|
|
||||||
try {
|
|
||||||
Add-Type -AssemblyName System.Windows.Forms
|
|
||||||
Add-Type -AssemblyName Microsoft.VisualBasic
|
|
||||||
|
|
||||||
# Use Windows built-in audio conversion
|
|
||||||
$shell = New-Object -ComObject Shell.Application
|
|
||||||
# This is a fallback method - may not work on all systems
|
|
||||||
Write-Host "⚠ FFmpeg not found. Please install FFmpeg or convert manually." -ForegroundColor Red
|
|
||||||
} catch {
|
|
||||||
Write-Host "⚠ Could not convert $mp3File automatically." -ForegroundColor Red
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host "⚠ File not found: $mp3Path" -ForegroundColor Red
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "`nConversion complete! If FFmpeg was not found, please:" -ForegroundColor Cyan
|
|
||||||
Write-Host "1. Install FFmpeg: https://ffmpeg.org/download.html" -ForegroundColor White
|
|
||||||
Write-Host "2. Or use an audio converter like Audacity" -ForegroundColor White
|
|
||||||
Write-Host "3. Convert to: 44.1kHz, 16-bit, Stereo WAV" -ForegroundColor White
|
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ Sound=1
|
|||||||
SmoothScroll=1
|
SmoothScroll=1
|
||||||
|
|
||||||
[Player]
|
[Player]
|
||||||
Name=GREGOR
|
Name=PLAYER
|
||||||
|
|
||||||
[Debug]
|
[Debug]
|
||||||
Enabled=1
|
Enabled=1
|
||||||
|
|||||||
@@ -558,16 +558,20 @@ bool ApplicationManager::initializeGame() {
|
|||||||
Audio::instance().init();
|
Audio::instance().init();
|
||||||
// Discover available tracks (up to 100) and queue for background loading
|
// Discover available tracks (up to 100) and queue for background loading
|
||||||
m_totalTracks = 0;
|
m_totalTracks = 0;
|
||||||
|
std::vector<std::string> trackPaths;
|
||||||
|
trackPaths.reserve(100);
|
||||||
for (int i = 1; i <= 100; ++i) {
|
for (int i = 1; i <= 100; ++i) {
|
||||||
char buf[128];
|
char base[128];
|
||||||
std::snprintf(buf, sizeof(buf), "assets/music/music%03d.mp3", i);
|
std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
|
||||||
// Use simple file existence check via std::filesystem
|
std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
|
||||||
if (std::filesystem::exists(buf)) {
|
if (path.empty()) {
|
||||||
Audio::instance().addTrackAsync(buf);
|
|
||||||
++m_totalTracks;
|
|
||||||
} else {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
trackPaths.push_back(path);
|
||||||
|
}
|
||||||
|
m_totalTracks = static_cast<int>(trackPaths.size());
|
||||||
|
for (const auto& path : trackPaths) {
|
||||||
|
Audio::instance().addTrackAsync(path);
|
||||||
}
|
}
|
||||||
if (m_totalTracks > 0) {
|
if (m_totalTracks > 0) {
|
||||||
Audio::instance().startBackgroundLoading();
|
Audio::instance().startBackgroundLoading();
|
||||||
|
|||||||
@@ -208,27 +208,14 @@ bool AssetManager::loadSoundEffectWithFallback(const std::string& id, const std:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try WAV first, then MP3 fallback (matching main.cpp pattern)
|
const std::string basePath = "assets/music/" + baseName;
|
||||||
std::string wavPath = "assets/music/" + baseName + ".wav";
|
std::string resolved = AssetPath::resolveWithExtensions(basePath, { ".wav", ".mp3" });
|
||||||
std::string mp3Path = "assets/music/" + baseName + ".mp3";
|
if (!resolved.empty() && m_soundSystem->loadSound(id, resolved)) {
|
||||||
|
logInfo("Loaded sound effect: " + id + " from " + resolved);
|
||||||
// Check WAV first
|
return true;
|
||||||
if (fileExists(wavPath)) {
|
|
||||||
if (m_soundSystem->loadSound(id, wavPath)) {
|
|
||||||
logInfo("Loaded sound effect: " + id + " from " + wavPath + " (WAV)");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to MP3
|
setError("Failed to load sound effect: " + id + " (no supported audio extension found)");
|
||||||
if (fileExists(mp3Path)) {
|
|
||||||
if (m_soundSystem->loadSound(id, mp3Path)) {
|
|
||||||
logInfo("Loaded sound effect: " + id + " from " + mp3Path + " (MP3 fallback)");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setError("Failed to load sound effect: " + id + " (tried both WAV and MP3)");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+67
-70
@@ -15,6 +15,7 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "audio/Audio.h"
|
#include "audio/Audio.h"
|
||||||
#include "audio/SoundEffect.h"
|
#include "audio/SoundEffect.h"
|
||||||
@@ -682,11 +683,11 @@ int main(int, char **)
|
|||||||
}
|
}
|
||||||
SDL_SetRenderVSync(renderer, 1);
|
SDL_SetRenderVSync(renderer, 1);
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
|
||||||
// On macOS bundles launched from Finder start in /, so re-root relative paths.
|
|
||||||
if (const char* basePathRaw = SDL_GetBasePath()) {
|
if (const char* basePathRaw = SDL_GetBasePath()) {
|
||||||
std::filesystem::path exeDir(basePathRaw);
|
std::filesystem::path exeDir(basePathRaw);
|
||||||
SDL_free(const_cast<char*>(basePathRaw));
|
AssetPath::setBasePath(exeDir.string());
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
// On macOS bundles launched from Finder start in /, so re-root relative paths.
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
std::filesystem::current_path(exeDir, ec);
|
std::filesystem::current_path(exeDir, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
@@ -694,12 +695,13 @@ int main(int, char **)
|
|||||||
"Failed to set working directory to %s: %s",
|
"Failed to set working directory to %s: %s",
|
||||||
exeDir.string().c_str(), ec.message().c_str());
|
exeDir.string().c_str(), ec.message().c_str());
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
SDL_free(const_cast<char*>(basePathRaw));
|
||||||
} else {
|
} else {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_GetBasePath() failed; asset lookups rely on current directory: %s",
|
"SDL_GetBasePath() failed; asset lookups rely on current directory: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
FontAtlas font;
|
FontAtlas font;
|
||||||
font.init("FreeSans.ttf", 24);
|
font.init("FreeSans.ttf", 24);
|
||||||
@@ -709,10 +711,15 @@ int main(int, char **)
|
|||||||
pixelFont.init("assets/fonts/PressStart2P-Regular.ttf", 16);
|
pixelFont.init("assets/fonts/PressStart2P-Regular.ttf", 16);
|
||||||
|
|
||||||
ScoreManager scores;
|
ScoreManager scores;
|
||||||
// Load scores asynchronously to prevent startup hang due to network request
|
// Load scores asynchronously but keep the worker alive until shutdown to avoid lifetime issues
|
||||||
std::thread([&scores]() {
|
std::jthread scoreLoader([&scores]() {
|
||||||
scores.load();
|
scores.load();
|
||||||
}).detach();
|
});
|
||||||
|
const auto ensureScoresLoaded = [&]() {
|
||||||
|
if (scoreLoader.joinable()) {
|
||||||
|
scoreLoader.join();
|
||||||
|
}
|
||||||
|
};
|
||||||
Starfield starfield;
|
Starfield starfield;
|
||||||
starfield.init(200, LOGICAL_W, LOGICAL_H);
|
starfield.init(200, LOGICAL_W, LOGICAL_H);
|
||||||
Starfield3D starfield3D;
|
Starfield3D starfield3D;
|
||||||
@@ -771,9 +778,19 @@ int main(int, char **)
|
|||||||
|
|
||||||
// Initialize sound effects system
|
// Initialize sound effects system
|
||||||
SoundEffectManager::instance().init();
|
SoundEffectManager::instance().init();
|
||||||
|
|
||||||
// Load sound effects
|
auto loadAudioAsset = [](const std::string& basePath, const std::string& id) {
|
||||||
SoundEffectManager::instance().loadSound("clear_line", "assets/music/clear_line.wav");
|
std::string resolved = AssetPath::resolveWithExtensions(basePath, { ".wav", ".mp3" });
|
||||||
|
if (resolved.empty()) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Missing audio asset for %s (base %s)", id.c_str(), basePath.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!SoundEffectManager::instance().loadSound(id, resolved)) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load %s from %s", id.c_str(), resolved.c_str());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadAudioAsset("assets/music/clear_line", "clear_line");
|
||||||
|
|
||||||
// Load voice lines for line clears using WAV files (with MP3 fallback)
|
// Load voice lines for line clears using WAV files (with MP3 fallback)
|
||||||
std::vector<std::string> singleSounds = {"well_played", "smooth_clear", "great_move"};
|
std::vector<std::string> singleSounds = {"well_played", "smooth_clear", "great_move"};
|
||||||
@@ -789,49 +806,25 @@ int main(int, char **)
|
|||||||
appendVoices(tripleSounds);
|
appendVoices(tripleSounds);
|
||||||
appendVoices(tetrisSounds);
|
appendVoices(tetrisSounds);
|
||||||
|
|
||||||
// Helper function to load sound with WAV/MP3 fallback and file existence check
|
auto loadVoice = [&](const std::string& id, const std::string& baseName) {
|
||||||
auto loadSoundWithFallback = [&](const std::string& id, const std::string& baseName) {
|
loadAudioAsset("assets/music/" + baseName, id);
|
||||||
std::string wavPath = "assets/music/" + baseName + ".wav";
|
|
||||||
std::string mp3Path = "assets/music/" + baseName + ".mp3";
|
|
||||||
|
|
||||||
// Check if WAV file exists first
|
|
||||||
SDL_IOStream* wavFile = SDL_IOFromFile(wavPath.c_str(), "rb");
|
|
||||||
if (wavFile) {
|
|
||||||
SDL_CloseIO(wavFile);
|
|
||||||
if (SoundEffectManager::instance().loadSound(id, wavPath)) {
|
|
||||||
(void)0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to MP3 if WAV doesn't exist or fails to load
|
|
||||||
SDL_IOStream* mp3File = SDL_IOFromFile(mp3Path.c_str(), "rb");
|
|
||||||
if (mp3File) {
|
|
||||||
SDL_CloseIO(mp3File);
|
|
||||||
if (SoundEffectManager::instance().loadSound(id, mp3Path)) {
|
|
||||||
(void)0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load sound: %s (tried both WAV and MP3)", id.c_str());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
loadSoundWithFallback("nice_combo", "nice_combo");
|
loadVoice("nice_combo", "nice_combo");
|
||||||
loadSoundWithFallback("you_fire", "you_fire");
|
loadVoice("you_fire", "you_fire");
|
||||||
loadSoundWithFallback("well_played", "well_played");
|
loadVoice("well_played", "well_played");
|
||||||
loadSoundWithFallback("keep_that_ryhtm", "keep_that_ryhtm");
|
loadVoice("keep_that_ryhtm", "keep_that_ryhtm");
|
||||||
loadSoundWithFallback("great_move", "great_move");
|
loadVoice("great_move", "great_move");
|
||||||
loadSoundWithFallback("smooth_clear", "smooth_clear");
|
loadVoice("smooth_clear", "smooth_clear");
|
||||||
loadSoundWithFallback("impressive", "impressive");
|
loadVoice("impressive", "impressive");
|
||||||
loadSoundWithFallback("triple_strike", "triple_strike");
|
loadVoice("triple_strike", "triple_strike");
|
||||||
loadSoundWithFallback("amazing", "amazing");
|
loadVoice("amazing", "amazing");
|
||||||
loadSoundWithFallback("you_re_unstoppable", "you_re_unstoppable");
|
loadVoice("you_re_unstoppable", "you_re_unstoppable");
|
||||||
loadSoundWithFallback("boom_tetris", "boom_tetris");
|
loadVoice("boom_tetris", "boom_tetris");
|
||||||
loadSoundWithFallback("wonderful", "wonderful");
|
loadVoice("wonderful", "wonderful");
|
||||||
loadSoundWithFallback("lets_go", "lets_go"); // For level up
|
loadVoice("lets_go", "lets_go");
|
||||||
loadSoundWithFallback("hard_drop", "hard_drop_001");
|
loadVoice("hard_drop", "hard_drop_001");
|
||||||
loadSoundWithFallback("new_level", "new_level");
|
loadVoice("new_level", "new_level");
|
||||||
|
|
||||||
bool suppressLineVoiceForLevelUp = false;
|
bool suppressLineVoiceForLevelUp = false;
|
||||||
|
|
||||||
@@ -1122,6 +1115,7 @@ int main(int, char **)
|
|||||||
playerName.pop_back();
|
playerName.pop_back();
|
||||||
} else if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) {
|
} else if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) {
|
||||||
if (playerName.empty()) playerName = "PLAYER";
|
if (playerName.empty()) playerName = "PLAYER";
|
||||||
|
ensureScoresLoaded();
|
||||||
scores.submit(game.score(), game.lines(), game.level(), game.elapsed(), playerName);
|
scores.submit(game.score(), game.lines(), game.level(), game.elapsed(), playerName);
|
||||||
Settings::instance().setPlayerName(playerName);
|
Settings::instance().setPlayerName(playerName);
|
||||||
isNewHighScore = false;
|
isNewHighScore = false;
|
||||||
@@ -1379,6 +1373,7 @@ int main(int, char **)
|
|||||||
SDL_StartTextInput(window);
|
SDL_StartTextInput(window);
|
||||||
} else {
|
} else {
|
||||||
isNewHighScore = false;
|
isNewHighScore = false;
|
||||||
|
ensureScoresLoaded();
|
||||||
scores.submit(game.score(), game.lines(), game.level(), game.elapsed());
|
scores.submit(game.score(), game.lines(), game.level(), game.elapsed());
|
||||||
}
|
}
|
||||||
state = AppState::GameOver;
|
state = AppState::GameOver;
|
||||||
@@ -1396,25 +1391,21 @@ int main(int, char **)
|
|||||||
|
|
||||||
// Count actual music files first
|
// Count actual music files first
|
||||||
totalTracks = 0;
|
totalTracks = 0;
|
||||||
for (int i = 1; i <= 100; ++i) { // Check up to 100 files
|
std::vector<std::string> trackPaths;
|
||||||
char buf[64];
|
trackPaths.reserve(100);
|
||||||
std::snprintf(buf, sizeof(buf), "assets/music/music%03d.mp3", i);
|
for (int i = 1; i <= 100; ++i) {
|
||||||
|
char base[64];
|
||||||
// Check if file exists
|
std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
|
||||||
SDL_IOStream* file = SDL_IOFromFile(buf, "rb");
|
std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
|
||||||
if (file) {
|
if (path.empty()) {
|
||||||
SDL_CloseIO(file);
|
break;
|
||||||
totalTracks++;
|
|
||||||
} else {
|
|
||||||
break; // No more consecutive files
|
|
||||||
}
|
}
|
||||||
|
trackPaths.push_back(path);
|
||||||
}
|
}
|
||||||
|
totalTracks = static_cast<int>(trackPaths.size());
|
||||||
// Add all found tracks to the background loading queue
|
|
||||||
for (int i = 1; i <= totalTracks; ++i) {
|
for (const auto& track : trackPaths) {
|
||||||
char buf[64];
|
Audio::instance().addTrackAsync(track);
|
||||||
std::snprintf(buf, sizeof(buf), "assets/music/music%03d.mp3", i);
|
|
||||||
Audio::instance().addTrackAsync(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start background loading thread
|
// Start background loading thread
|
||||||
@@ -1470,7 +1461,12 @@ int main(int, char **)
|
|||||||
static bool menuTrackLoaded = false;
|
static bool menuTrackLoaded = false;
|
||||||
if (!menuTrackLoaded) {
|
if (!menuTrackLoaded) {
|
||||||
std::thread([]() {
|
std::thread([]() {
|
||||||
Audio::instance().setMenuTrack("assets/music/Every Block You Take.mp3");
|
std::string menuTrack = AssetPath::resolveWithExtensions("assets/music/Every Block You Take", { ".mp3" });
|
||||||
|
if (!menuTrack.empty()) {
|
||||||
|
Audio::instance().setMenuTrack(menuTrack);
|
||||||
|
} else {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Menu track not found (Every Block You Take)");
|
||||||
|
}
|
||||||
}).detach();
|
}).detach();
|
||||||
menuTrackLoaded = true;
|
menuTrackLoaded = true;
|
||||||
}
|
}
|
||||||
@@ -1804,6 +1800,7 @@ int main(int, char **)
|
|||||||
// 4. Draw Text
|
// 4. Draw Text
|
||||||
// 4. Draw Text
|
// 4. Draw Text
|
||||||
// Title
|
// Title
|
||||||
|
ensureScoresLoaded();
|
||||||
bool realHighScore = scores.isHighScore(game.score());
|
bool realHighScore = scores.isHighScore(game.score());
|
||||||
const char* title = realHighScore ? "NEW HIGH SCORE!" : "GAME OVER";
|
const char* title = realHighScore ? "NEW HIGH SCORE!" : "GAME OVER";
|
||||||
int tW=0, tH=0; pixelFont.measure(title, 2.0f, tW, tH);
|
int tW=0, tH=0; pixelFont.measure(title, 2.0f, tW, tH);
|
||||||
|
|||||||
@@ -2,17 +2,27 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <initializer_list>
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
namespace AssetPath {
|
namespace AssetPath {
|
||||||
|
|
||||||
inline bool fileExists(const std::string& path) {
|
inline std::string& baseDirectory() {
|
||||||
if (path.empty()) {
|
static std::string base;
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void setBasePath(std::string path) {
|
||||||
|
baseDirectory() = std::move(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool tryOpenFile(const std::string& candidate) {
|
||||||
|
if (candidate.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
SDL_IOStream* file = SDL_IOFromFile(candidate.c_str(), "rb");
|
||||||
SDL_IOStream* file = SDL_IOFromFile(path.c_str(), "rb");
|
|
||||||
if (file) {
|
if (file) {
|
||||||
SDL_CloseIO(file);
|
SDL_CloseIO(file);
|
||||||
return true;
|
return true;
|
||||||
@@ -20,13 +30,58 @@ inline bool fileExists(const std::string& path) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool fileExists(const std::string& path) {
|
||||||
|
if (path.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tryOpenFile(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path p(path);
|
||||||
|
if (!p.is_absolute()) {
|
||||||
|
const std::string& base = baseDirectory();
|
||||||
|
if (!base.empty()) {
|
||||||
|
std::filesystem::path combined = std::filesystem::path(base) / p;
|
||||||
|
if (tryOpenFile(combined.string())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string resolveWithBase(const std::string& path) {
|
||||||
|
if (path.empty()) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tryOpenFile(path)) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path p(path);
|
||||||
|
if (!p.is_absolute()) {
|
||||||
|
const std::string& base = baseDirectory();
|
||||||
|
if (!base.empty()) {
|
||||||
|
std::filesystem::path combined = std::filesystem::path(base) / p;
|
||||||
|
std::string combinedStr = combined.string();
|
||||||
|
if (tryOpenFile(combinedStr)) {
|
||||||
|
return combinedStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
inline std::string resolveImagePath(const std::string& originalPath) {
|
inline std::string resolveImagePath(const std::string& originalPath) {
|
||||||
if (originalPath.empty()) {
|
if (originalPath.empty()) {
|
||||||
return originalPath;
|
return originalPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileExists(originalPath)) {
|
if (auto resolved = resolveWithBase(originalPath); resolved != originalPath || fileExists(resolved)) {
|
||||||
return originalPath;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t dot = originalPath.find_last_of('.');
|
const std::size_t dot = originalPath.find_last_of('.');
|
||||||
@@ -45,12 +100,41 @@ inline std::string resolveImagePath(const std::string& originalPath) {
|
|||||||
if (candidate == originalPath) {
|
if (candidate == originalPath) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (fileExists(candidate)) {
|
std::string resolvedCandidate = resolveWithBase(candidate);
|
||||||
return candidate;
|
if (resolvedCandidate != candidate || fileExists(resolvedCandidate)) {
|
||||||
|
return resolvedCandidate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalPath;
|
return originalPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::string resolveWithExtensions(const std::string& basePathWithoutExt, std::initializer_list<const char*> extensions) {
|
||||||
|
for (const char* ext : extensions) {
|
||||||
|
std::string candidate = basePathWithoutExt + ext;
|
||||||
|
std::string resolved = resolveWithBase(candidate);
|
||||||
|
if (resolved != candidate || fileExists(resolved)) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string resolveAudioPath(const std::string& basePathWithoutExt) {
|
||||||
|
static constexpr std::array<const char*, 4> AUDIO_EXTENSIONS = {
|
||||||
|
".ogg",
|
||||||
|
".flac",
|
||||||
|
".wav",
|
||||||
|
".mp3"
|
||||||
|
};
|
||||||
|
for (const char* ext : AUDIO_EXTENSIONS) {
|
||||||
|
std::string candidate = basePathWithoutExt + ext;
|
||||||
|
std::string resolved = resolveWithBase(candidate);
|
||||||
|
if (resolved != candidate || fileExists(resolved)) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace AssetPath
|
} // namespace AssetPath
|
||||||
|
|||||||
Reference in New Issue
Block a user