#include "hitobject.h" #include "audio.h" #include "beatmap.h" #include "beatmapreader.h" #include "graphics.h" #include "util.h" #include #include #include SDL_EnumerationResult handleCountBeatmapHitObjects(char* section, char* /*values*/[], size_t /*valuesLen*/, void* userdata) { if (strcmp(section, "HitObjects") == 0) { (*(size_t*)userdata)++; } return SDL_ENUM_CONTINUE; } SDL_EnumerationResult handleBeatmapHitObjectCSVRow(char* section, char* values[], size_t valuesLen, void* userdata) { if (strcmp(section, "HitObjects") != 0) { return SDL_ENUM_CONTINUE; } struct Osu_HitObjects* data = userdata; SDL_assert(valuesLen >= 5); struct Osu_HitObject* hitObject = &data->objects[data->len]; data->len++; // FIXME: Error checking hitObject->x = strtof(values[0], NULL); hitObject->y = strtof(values[1], NULL); hitObject->time = strtol(values[2], NULL, 0); hitObject->hitSounds = strtol(values[4], NULL, 0) & 0b1111; Uint8 type = strtol(values[3], NULL, 0); if (type & (1 << 0)) { hitObject->type = OSU_HITOBJECT_TYPE_CIRCLE; } else if (type & (1 << 1)) { hitObject->type = OSU_HITOBJECT_TYPE_SLIDER; } else if (type & (1 << 3)) { hitObject->type = OSU_HITOBJECT_TYPE_SPINNER; } OSU_LOG_TRACEF("%u at %.1f,%.1f at time %lu\n", hitObject->type, hitObject->x, hitObject->y, hitObject->time); return SDL_ENUM_CONTINUE; } struct Osu_HitObjects* Osu_LoadHitObjectsFromBeatmap(struct Osu_Beatmap* beatmap) { OSU_LOG_TRACEF("Loading beatmap file: %s", beatmap->filepath); char* file = SDL_LoadFile(beatmap->filepath, NULL); if (!file) { OSU_LOG_TRACEF("Unable to load file: %s", SDL_GetError()); return NULL; } struct Osu_HitObjects* ho = malloc(sizeof(struct Osu_HitObjects)); SDL_zero(*ho); size_t numHitObjects = 0; Osu_EnumerateBeatmapData(file, NULL, &handleCountBeatmapHitObjects, &numHitObjects); ho->objects = calloc(numHitObjects, sizeof(struct Osu_HitObject)); if (!Osu_EnumerateBeatmapData(file, NULL, &handleBeatmapHitObjectCSVRow, ho)) { Osu_DestroyHitObjects(ho); SDL_free(file); return NULL; } OSU_LOG_TRACEF("%zu hit objects\n", ho->len); SDL_free(file); return ho; } void Osu_DestroyHitObjects(struct Osu_HitObjects* ho) { free(ho->objects); free(ho); } void Osu_UpdateHitObjects(struct Osu_HitObjects* ho, struct Osu_Game* game) { if (ho->cursor == ho->len) { return; } SDL_assert(ho->cursor < ho->len); size_t cursor = ho->cursor; while (cursor < ho->len) { struct Osu_HitObject* obj = &ho->objects[cursor]; if (obj->time + OSU_HITOBJECT_LEAD_OUT < game->beatmapTick) { break; } if (obj->time == game->beatmapTick) { Osu_PlayHitSounds(game->audio, obj); } cursor++; } // TODO: Two cursors - one for the next object to hit and one for the next object to draw. while (ho->cursor < ho->len && ho->objects[ho->cursor].time + OSU_HITOBJECT_LEAD_OUT < game->beatmapTick) { ho->cursor++; } } float min(float a, float b) { return a < b ? a : b; } void Osu_RenderHitObjects(struct Osu_HitObjects* ho, struct Osu_Graphics* graphics, Sint32 tick) { if (ho->cursor > ho->len) { return; } SDL_FRect bgRect = { .x = 200, .y = 200, .w = 512 * 3, .h = 384 * 3, }; SDL_SetRenderDrawBlendMode(graphics->renderer, SDL_BLENDMODE_NONE); SDL_SetRenderDrawColor(graphics->renderer, 255, 255, 255, 255); SDL_RenderRect(graphics->renderer, &bgRect); SDL_FRect destRect = {}; SDL_GetTextureSize(graphics->hitCircleTexture, &destRect.w, &destRect.h); destRect.w *= 1.5; destRect.h *= 1.5; size_t cursor = ho->cursor; while (cursor < ho->len && ho->objects[cursor].time - OSU_HITOBJECT_LEAD_IN <= tick) { struct Osu_HitObject* obj = &ho->objects[cursor]; float alpha = 1.0f; if (tick < obj->time) { alpha = min((1.0f - (float)(obj->time - tick) / OSU_HITOBJECT_LEAD_IN) * 1.25, 1.0f); } else if (tick > obj->time) { alpha = 1.0f - (float)(tick - obj->time) / OSU_HITOBJECT_LEAD_OUT; } destRect.x = obj->x * 3 + 200 - destRect.w / 2; destRect.y = obj->y * 3 + 200 - destRect.h / 2; SDL_Color color = {}; switch (obj->type) { case OSU_HITOBJECT_TYPE_CIRCLE: color = (SDL_Color){26, 116, 242, 255}; break; case OSU_HITOBJECT_TYPE_SLIDER: color = (SDL_Color){164, 32, 240, 255}; break; case OSU_HITOBJECT_TYPE_SPINNER: color = (SDL_Color){37, 185, 239, 255}; break; } SDL_SetTextureColorMod(graphics->hitCircleTexture, color.r, color.g, color.b); SDL_SetTextureAlphaModFloat(graphics->hitCircleOverlayTexture, alpha); SDL_RenderTexture(graphics->renderer, graphics->hitCircleTexture, NULL, &destRect); SDL_RenderTexture(graphics->renderer, graphics->hitCircleOverlayTexture, NULL, &destRect); if (tick < obj->time) { SDL_FRect acRect = {}; SDL_GetTextureSize(graphics->approachCircleTexture, &acRect.w, &acRect.h); float ratio = (float)(obj->time - tick) / OSU_HITOBJECT_LEAD_IN; float sizeRatio = 1.5f * (1.0f - ratio) + (5.0f * ratio); acRect.w *= sizeRatio; acRect.h *= sizeRatio; acRect.x = obj->x * 3 + 200 - acRect.w / 2; acRect.y = obj->y * 3 + 200 - acRect.h / 2; SDL_SetTextureColorMod(graphics->approachCircleTexture, color.r, color.g, color.b); SDL_SetTextureAlphaModFloat(graphics->approachCircleTexture, alpha); SDL_RenderTexture(graphics->renderer, graphics->approachCircleTexture, NULL, &acRect); } cursor++; } }