#include "game.h" #include "audio.h" #include "beatmap.h" #include "beatmapselector.h" #include "beatmapstore.h" #include "defines.h" #include "frametiming.h" #include "graphics.h" #include "hitobject.h" #include "util.h" #include #include #include #include #include struct Osu_Game* Osu_CreateGame() { struct Osu_Game* game = calloc(1, sizeof(struct Osu_Game)); const char* basePath; SDL_SAFEASSN(basePath, SDL_GetBasePath); SDL_asprintf(&game->dataPath, "%s", basePath); dirname(game->dataPath); game->audio = Osu_CreateAudio(game->dataPath); game->beatmaps = Osu_CreateBeatmapStore(game->dataPath); game->graphics = Osu_CreateGraphics(game->dataPath); game->phase = OSU_GAMEPHASE_LOADING_BEATMAPS; return game; } void Osu_DestroyGame(struct Osu_Game* game) { if (game->hitObjects) { Osu_DestroyHitObjects(game->hitObjects); } if (game->beatmapSelector) { Osu_DestroyBeatmapSelector(game, game->beatmapSelector); } Osu_DestroyBeatmapStore(game->beatmaps); Osu_DestroyAudio(game->audio); Osu_DestroyGraphics(game->graphics); free(game->dataPath); SDL_Quit(); free(game); } void Osu_PreUpdateGame(struct Osu_Game* game) { struct Osu_BeatmapStore* beatmaps = game->beatmaps; if (game->phase == OSU_GAMEPHASE_LOADING_BEATMAPS) { SDL_LockMutex(beatmaps->mutex); if (beatmaps->finishedLoading && !game->beatmapSelector) { game->beatmapSelector = Osu_CreateBeatmapSelector(game, game->beatmaps); Osu_SetSelectedBeatmap(game, beatmaps->beatmaps[Osu_Random(0, game->beatmaps->beatmapsLen - 1)]); game->phase = OSU_GAMEPHASE_SONG_SELECTION; } SDL_UnlockMutex(beatmaps->mutex); } } void Osu_UpdateGame(struct Osu_Game* game) { if (game->phase != OSU_GAMEPHASE_IN_GAME) { return; } if (game->beatmapPaused) { return; } if (game->beatmapTick == 0) { Osu_PlayMusic(game->audio); } Osu_UpdateHitObjects(game->hitObjects, game); if (game->beatmapTick == 700 && !game->audioSynced) { game->audioSynced = true; game->beatmapTick = MIX_AudioFramesToMS(game->audio->music, MIX_GetTrackPlaybackPosition(game->audio->musicTrack)); } else { game->beatmapTick++; } } void Osu_PostUpdateGame(struct Osu_Game* game) { if (game->phase != OSU_GAMEPHASE_IN_GAME) { return; } if (game->beatmapTick > 0) { double musicPosition = MIX_TrackFramesToMS(game->audio->musicTrack, MIX_GetTrackPlaybackPosition(game->audio->musicTrack)); float audioLatency = musicPosition - game->beatmapTick; game->audioLatencySamples[game->audioLatencySampleIndex] = audioLatency; game->audioLatencySampleIndex = (game->audioLatencySampleIndex + 1) % OSU_AUDIO_LATENCY_SAMPLES; game->musicPosition = (Uint64)musicPosition; } if (game->hitObjects->cursor >= game->hitObjects->len) { Osu_ExitBeatmap(game); } } bool Osu_HandleGameEvent(struct Osu_Game* game, union SDL_Event* event) { if (event->type == SDL_EVENT_QUIT) { return true; } if (game->beatmapSelector) { Osu_HandleBeatmapSelectorEvent(game, game->beatmapSelector, event); } if (game->phase == OSU_GAMEPHASE_IN_GAME) { if (event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_ESCAPE) { Osu_ExitBeatmap(game); } else if (event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_SPACE) { if (game->beatmapPaused) { game->beatmapPaused = false; MIX_ResumeTrack(game->audio->musicTrack); } else { game->beatmapPaused = true; MIX_PauseTrack(game->audio->musicTrack); } } } if (event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_EQUALS) { game->graphics->beatmapDimFactor += 10; } else if (event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_MINUS) { game->graphics->beatmapDimFactor -= 10; } return false; } void Osu_RenderGame(struct Osu_Game* game) { Osu_PrepareRender(game->graphics); SDL_SetRenderDrawColor(game->graphics->renderer, 0, 0, 0, 1); SDL_RenderClear(game->graphics->renderer); Osu_RenderWallpaper(game->graphics); if (game->phase == OSU_GAMEPHASE_IN_GAME) { SDL_SetRenderDrawBlendMode(game->graphics->renderer, SDL_BLENDMODE_MUL); // TODO: Make adjustable SDL_SetRenderDrawColor(game->graphics->renderer, 0, 0, 0, game->graphics->beatmapDimFactor); SDL_RenderFillRect(game->graphics->renderer, &game->graphics->screenRect); } if (game->beatmapSelector) { Osu_RenderBeatmapSelector(game->beatmapSelector, game->graphics); } if (game->hitObjects) { Osu_RenderHitObjects(game->hitObjects, game->graphics, game->beatmapTick); } Osu_RenderDebugText(game); Osu_RenderCursor(game->graphics); SDL_RenderPresent(game->graphics->renderer); } void Osu_RenderDebugText(struct Osu_Game* game) { float averageAudioLatency = 0; for (size_t i = 0; i < OSU_AUDIO_LATENCY_SAMPLES; i++) { averageAudioLatency += game->audioLatencySamples[i]; } averageAudioLatency /= OSU_AUDIO_LATENCY_SAMPLES; SDL_LockMutex(game->beatmaps->mutex); size_t beatmapsLen = game->beatmaps->beatmapsLen; SDL_UnlockMutex(game->beatmaps->mutex); char buf[2048]; double avgFPS = (double)1000000000 / game->frameTiming.avgFrameDuration; snprintf(buf, sizeof(buf), "FPS: %.2f\nBeatmaps loaded: %zu\nSearch: %s\nTick: %i\nAudio progress: %lu\nLatency: %.2fms", avgFPS, beatmapsLen, game->beatmapSelector ? game->beatmapSelector->searchText : "", game->beatmapTick, game->musicPosition, averageAudioLatency); TTF_SetTextString(game->graphics->debugText, buf, 0); int w, h; TTF_GetTextSize(game->graphics->debugText, &w, &h); SDL_FRect backgroundRect = { .x = 5, .y = 5, .w = w + 10, .h = h + 10, }; SDL_SetRenderDrawColor(game->graphics->renderer, 0, 0, 0, 255); SDL_RenderFillRect(game->graphics->renderer, &backgroundRect); TTF_DrawRendererText(game->graphics->debugText, 10, 10); } void Osu_SetSelectedBeatmap(struct Osu_Game* game, struct Osu_Beatmap* beatmap) { game->beatmapSelector->selectedBeatmap = beatmap; Osu_SetMusic(game->audio, beatmap->audioFilename, beatmap->previewTime, true); Osu_PlayMusic(game->audio); if (beatmap->backgroundPath) { Osu_SetWallpaper(game->graphics, beatmap->backgroundPath); } } void Osu_EnterBeatmap(struct Osu_Game* game, struct Osu_Beatmap* beatmap) { if (game->hitObjects) { Osu_DestroyHitObjects(game->hitObjects); } game->activeBeatmap = beatmap; game->hitObjects = Osu_LoadHitObjectsFromBeatmap(beatmap); game->beatmapTick = -beatmap->audioLeadIn - 2000; game->phase = OSU_GAMEPHASE_IN_GAME; Osu_SetMusic(game->audio, beatmap->audioFilename, 0, false); memset(game->audioLatencySamples, 0, sizeof(game->audioLatencySamples)); game->audioLatencySampleIndex = 0; Osu_DestroyBeatmapSelector(game, game->beatmapSelector); game->beatmapSelector = NULL; } void Osu_ExitBeatmap(struct Osu_Game* game) { SDL_assert(game->phase == OSU_GAMEPHASE_IN_GAME); game->phase = OSU_GAMEPHASE_SONG_SELECTION; Osu_DestroyHitObjects(game->hitObjects); game->hitObjects = NULL; game->beatmapSelector = Osu_CreateBeatmapSelector(game, game->beatmaps); Osu_SetSelectedBeatmap(game, game->activeBeatmap); game->activeBeatmap = NULL; game->beatmapTick = 0; game->audioSynced = false; }