// // Created by bruno on 7.6.2025. // #include #include #include "entity.h" #include "../player/player.h" #include "../util/pathfinding.h" #include "../util/font.h" #include "../util/audio.h" EntityArray entities; EntityTypeReg EntityRegistry[ENTITY_MAX_COUNT]; void renderEntities(SDL_Renderer *renderer, SDL_Rect playerRect) { SDL_Texture *oldTarget = SDL_GetRenderTarget(renderer); SDL_SetRenderTarget(renderer, entityTexture); SDL_RenderClear(mainRenderer); for (int i = 0; i < entities.activeCount; i++) { Entity *ent = &entities.entities[i]; SDL_Rect renderRect = ent->renderRect; adjustRect(&renderRect, playerRect); if (!checkCollision(renderRect, screenRect)) { continue; } EntityTypeReg entType = EntityRegistry[ent->type]; char animationFrame = (animationStep / entType.animation.divisor) % entType.animation.frameCount; SDL_RenderCopy(renderer, atlasTexture, &entType.animation.atlasRects[animationFrame], &renderRect); char healthStr[12]; snprintf(healthStr, 12, "%d/%d", ent->health, entType.maxHealth); renderText(renderer, fonts[3], healthStr, renderRect.x, renderRect.y); } SDL_SetRenderTarget(renderer, oldTarget); } void updateEntities(Player *plr) { for (int i = 0; i < entities.activeCount; i++) { Entity *ent = &entities.entities[i]; EntityTypeReg entT = EntityRegistry[ent->type]; if (ent->health > entT.maxHealth || ent->health <= 0) { remove_entity(&entities, i); continue; } bool atTargetSnapshot = ent->tileRect.x == ent->targetSnapshot.x && ent->tileRect.y == ent->targetSnapshot.y; bool atTarget = ent->tileRect.x == ent->target.x && ent->tileRect.y == ent->target.y; if (animationStep >= ent->entityNextTick) { if (sqrt(pow(abs(plr->tileRect.x - ent->tileRect.x), 2) + pow(abs(plr->tileRect.y - ent->tileRect.y), 2)) < ENEMY_RANGE) { plr->health -= ENEMY_DAMAGE; } if (plr->tileRect.x) for (int y = ent->tileRect.y - ENEMY_RANGE; y < ent->tileRect.y + ENEMY_RANGE; y++) { if (y < 0 || y >= MAP_HEIGHT) { continue; } for (int x = ent->tileRect.x - ENEMY_RANGE; x < ent->tileRect.x + ENEMY_RANGE; x++) { if (x < 0 || x >= MAP_WIDTH) { continue; } Tile *targTile = &tileMap[y][x]; if (targTile->type == TYPE_AIR) { continue; } targTile->health -= ENEMY_DAMAGE; if (targTile->health <= 0) { if (targTile->audioCh < NUM_SYNTH_VOICES) { audioData.synthVoices[targTile->audioCh].volume = 0; } memset(targTile->items, 0, sizeof(targTile->items)); targTile->type = TYPE_AIR; } } } } // Retry pathfinding every 10 ticks if we don't have a path and aren't at the target bool shouldRetryPathfinding = ( ent->path.length == 0 && !atTarget && (animationStep % 10 == 0) ); // Also try pathfinding if we reached the snapshot (end of path) if (atTargetSnapshot || shouldRetryPathfinding) { if (isWalkable(ent->target) && find_path(ent->tileRect, ent->target)) { ent->path = reconstruct_path(ent->target); ent->path.stepIndex = 0; ent->targetSnapshot = ent->target; // snapshot the current target ent->fromTile = ent->tileRect; ent->toTile = ent->path.steps[0]; ent->interpolateTick = 0; ent->entityNextTick = animationStep + entT.entityTickRate; } else if (atTargetSnapshot) { // No path found and we finished current one — freeze ent->path.length = 0; continue; } } // Step 2: Movement if (ent->path.length > 0 && ent->path.stepIndex < ent->path.length && animationStep >= ent->entityNextTick) { ent->fromTile = ent->tileRect; ent->toTile = ent->path.steps[ent->path.stepIndex]; ent->tileRect = ent->toTile; ent->entityNextTick = animationStep + entT.entityTickRate; ent->interpolateTick = 0; ent->path.stepIndex++; } // Step 3: Interpolation MiniRect from = { .x = ent->fromTile.x * TILE_SIZE, .y = ent->fromTile.y * TILE_SIZE }; MiniRect to = { .x = ent->toTile.x * TILE_SIZE, .y = ent->toTile.y * TILE_SIZE }; float t = (float) ent->interpolateTick / entT.entityTickRate; ent->renderRect.x = (int) (from.x + (to.x - from.x) * t); ent->renderRect.y = (int) (from.y + (to.y - from.y) * t); if (ent->interpolateTick < entT.entityTickRate) { ent->interpolateTick++; } } } void registerEntity(char fname[20], SDL_Renderer *renderer) { char name[21]; // Load animation frames int frame = 0; int indexEntity = 0; char texturePath[80]; if (sscanf(fname, "%d_%20[^_]_%d.png", &indexEntity, name, &frame) == 3) { // Success: you now have index, fname, and frame } else { fprintf(stderr, "Invalid format: %s\n", fname); } strcpy(EntityRegistry[indexEntity].name, name); snprintf(texturePath, sizeof(texturePath), "./assets/entities/%s", fname); SDL_Texture *texture = IMG_LoadTexture(renderer, texturePath); if (!texture) { if (frame == 0) { fprintf(stderr, "Failed to load entity texture %s: %s\n", texturePath, IMG_GetError()); } } SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); //printf("Ent %s to %d\n", fname, indexEntity); EntityRegistry[indexEntity].animation.atlasRects[frame] = allocate_32x32(texture, renderer); EntityRegistry[indexEntity].type = indexEntity; EntityRegistry[indexEntity].animation.frameCount = frame + 1; EntityRegistry[indexEntity].animation.divisor = 1; EntityRegistry[indexEntity].entityTickRate = 8; EntityRegistry[indexEntity].maxHealth = 100; if (indexEntity + 1 > backgroundTileTypeIndex) { backgroundTileTypeIndex = indexEntity + 1; } } void loadEntities(SDL_Renderer *renderer) { DIR *dir = opendir("./assets/entities"); if (!dir) { perror("Failed to open entities directory"); return; } char *entityNames[ENTITY_MAX_COUNT]; int entityCount = 0; struct dirent *entry; while ((entry = readdir(dir))) { char *dot = strrchr(entry->d_name, '.'); if (!dot || strcmp(dot, ".png") != 0) continue; // Check if baseName already stored int found = 0; for (int i = 0; i < entityCount; ++i) { if (strcmp(entityNames[i], entry->d_name) == 0) { found = 1; break; } } if (!found && entityCount < ENTITY_MAX_COUNT) { entityNames[entityCount++] = strdup(entry->d_name); // Only store base, not full file name } } closedir(dir); qsort(entityNames, entityCount, sizeof(char *), compareStrings); // Call registerEntity on each base name for (int i = 0; i < entityCount; ++i) { char fileName[64]; snprintf(fileName, sizeof(fileName), "%s", entityNames[i]); registerEntity(fileName, renderer); free(entityNames[i]); } } int add_entity(EntityArray *arr, Entity t) { if (arr->activeCount >= ENTITY_MAX_COUNT) return 0; arr->entities[arr->activeCount] = t; arr->activeCount++; return arr->activeCount - 1; } void remove_entity(EntityArray *arr, int index) { if (index < 0 || index >= arr->activeCount) return; arr->activeCount--; arr->entities[index] = arr->entities[arr->activeCount]; // swap with last active }