diff --git a/CMakeLists.txt b/CMakeLists.txt index 6772f15..f9556c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,10 @@ set(SOURCE_FILES util/atlas.h tiles/miner.c tiles/miner.h + util/pathfinding.c + util/pathfinding.h + entity/entity.c + entity/entity.h ) add_executable(factorygame ${SOURCE_FILES}) diff --git a/assets/entities/00_enemy_00.png b/assets/entities/00_enemy_00.png new file mode 100644 index 0000000..96e9f23 Binary files /dev/null and b/assets/entities/00_enemy_00.png differ diff --git a/entity/entity.c b/entity/entity.c new file mode 100644 index 0000000..890c19c --- /dev/null +++ b/entity/entity.c @@ -0,0 +1,187 @@ +// +// Created by bruno on 7.6.2025. +// + +#include +#include +#include "entity.h" +#include "../player/player.h" +#include "../util/pathfinding.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); + } + SDL_SetRenderTarget(renderer, oldTarget); +} + +void updateEntities() { + for (int i = 0; i < entities.activeCount; i++) { + Entity *ent = &entities.entities[i]; + EntityTypeReg entT = EntityRegistry[ent->type]; + + // Step 1: If no path, or we're at the end of a path, check for re-path + bool atTargetSnapshot = ent->tileRect.x == ent->targetSnapshot.x && + ent->tileRect.y == ent->targetSnapshot.y; + + bool needsPath = ent->path.length == 0 || ent->path.stepIndex >= ent->path.length; + + if (needsPath && (atTargetSnapshot || ent->path.length == 0)) { + if (ent->tileRect.x != ent->target.x || ent->tileRect.y != ent->target.y) { + 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 { + // No path found — freeze + ent->path.length = 0; + continue; + } + } + } + + // Step 2: If it's time to move to the next tile + 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: Interpolate renderRect between fromTile and toTile + 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.textures[frame] = texture; + 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; + + 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 +} \ No newline at end of file diff --git a/entity/entity.h b/entity/entity.h new file mode 100644 index 0000000..220ff40 --- /dev/null +++ b/entity/entity.h @@ -0,0 +1,55 @@ +// +// Created by bruno on 7.6.2025. +// + +#ifndef FACTORYGAME_ENTITY_H +#define FACTORYGAME_ENTITY_H + +#include "../tiles/tile.h" +#include "../util/pathfinding.h" + +#define ENTITY_MAX_COUNT 1024 + +typedef enum EntityType { + GHOST, +} EntityType; + +typedef struct EntityTypeReg { + EntityType type; + Animation animation; + char name[20]; + int speed; + int entityTickRate; +} EntityTypeReg; + +typedef struct Entity { + MiniRect tileRect; + SDL_Rect renderRect; + EntityType type; + uint16_t health; + MiniRect target; + Path path; + int entityNextTick; + unsigned char interpolateTick; + MiniRect fromTile; + MiniRect toTile; + MiniRect targetSnapshot; +} Entity; + +typedef struct EntityArray { + Entity entities[ENTITY_MAX_COUNT]; + int activeCount; +} EntityArray; + +extern EntityArray entities; + +void remove_entity(EntityArray *arr, int index); + +int add_entity(EntityArray *arr, Entity t); + +void renderEntities(SDL_Renderer *renderer, SDL_Rect playerRect); +void updateEntities(); +void registerEntity(char fname[20], SDL_Renderer *renderer); +void loadEntities(SDL_Renderer *renderer); + +#endif //FACTORYGAME_ENTITY_H diff --git a/items/item.c b/items/item.c index 7387667..79fd339 100644 --- a/items/item.c +++ b/items/item.c @@ -246,7 +246,7 @@ void renderItem(ItemOnBelt item, SDL_Renderer *renderer, int lane, SDL_Rect play xOffset += perpX * laneSign * laneOffset; yOffset += perpY * laneSign * laneOffset; - //--- 5) apply to rect + //--- 5) apply to tileRect rect.x += (int) roundf(xOffset); rect.y += (int) roundf(yOffset); @@ -273,7 +273,7 @@ void renderItem(ItemOnBelt item, SDL_Renderer *renderer, int lane, SDL_Rect play adjustRect(&rectA, playerRect); SDL_RenderFillRect(renderer, &rectA); } - //SDL_RenderCopyx(renderer, ItemRegistry[item.type].textureOnBelt[ORIENT_LEFT], NULL, &rect); + //SDL_RenderCopyx(renderer, ItemRegistry[item.type].textureOnBelt[ORIENT_LEFT], NULL, &tileRect); SDL_RenderCopy(renderer, atlasTexture, &ItemRegistry[item.type].beltAnimation.atlasRects[ORIENT_LEFT][ (animationStep / ItemRegistry[item.type].beltAnimation.divisor) % diff --git a/main.c b/main.c index db90671..8884567 100644 --- a/main.c +++ b/main.c @@ -9,6 +9,7 @@ #include "player/player.h" #include "util/perlin.h" #include "util/atlas.h" +#include "entity/entity.h" typedef struct GameState { Player player; @@ -16,6 +17,9 @@ typedef struct GameState { BackgroundTile backgroundTileMap[MAP_HEIGHT][MAP_WIDTH]; AudioData audioData; TileArray neededUpdates; + EntityArray entities; + Node openList[MAX_OPEN_NODES]; + int openCount; } GameState; GameState gameState; @@ -40,6 +44,9 @@ int loadGameState(char *filename, Player *plr) { audioData.playerRect = tmp; audioData.totalSamples = 0; memcpy(&neededUpdates, &gameState.neededUpdates, sizeof(gameState.neededUpdates)); + memcpy(&entities, &gameState.entities, sizeof(gameState.entities)); + openCount = gameState.openCount; + memcpy(&openList, &gameState.openList, sizeof(gameState.openList)); plr->cursor.targetTile = NULL; plr->cursor.prevTargetTile = NULL; return 0; @@ -53,6 +60,9 @@ void saveGameState(char *filename, Player *plr) { memcpy(gameState.backgroundTileMap, backgroundMap, sizeof(gameState.backgroundTileMap)); memcpy(&gameState.audioData, &audioData, sizeof(gameState.audioData)); memcpy(&gameState.neededUpdates, &neededUpdates, sizeof(neededUpdates)); + memcpy(&gameState.entities, &entities, sizeof(entities)); + memcpy(&gameState.openList, &openList, sizeof(openList)); + gameState.openCount = openCount; FILE *gameSave = fopen(filename, "wb"); if (!gameSave) { @@ -142,6 +152,20 @@ int init() { loadBackgroundTiles(mainRenderer); loadTiles(mainRenderer); loadItems(mainRenderer); + loadEntities(mainRenderer); + + Entity entTest; + memset(&entTest, 0, sizeof(Entity)); + entTest.tileRect.x = 4; + entTest.tileRect.y = 5; + entTest.renderRect.w = TILE_SIZE; + entTest.renderRect.h = TILE_SIZE; + entTest.target.x = 0; + entTest.target.y = 0; + entTest.health = 100; + entTest.type = GHOST; + add_entity(&entities, entTest); + setupTiles(); // for (ItemType i = 0; i < ITEMREGISTRY_SIZE; i++) { @@ -226,6 +250,7 @@ int render() { rect2.h = ATLAS_SIZE; renderAllTiles(mainRenderer, player.rect); + renderEntities(mainRenderer, player.rect); renderPlayer(&player); @@ -333,6 +358,7 @@ void processMousePosition() { if (player.inventory.slotCounts[player.inventory.activeSlotIndex] > 0) { player.inventory.slotCounts[player.inventory.activeSlotIndex]--; player.cursor.targetTile->type = player.inventory.activeSlotIndex; + player.cursor.targetTile->health = TileRegistry[player.inventory.activeSlotIndex].maxHealth; player.cursor.targetTile->rect.x = player.cursor.tileX; player.cursor.targetTile->rect.y = player.cursor.tileY; if (TileRegistry[player.inventory.activeSlotIndex].needsTicks) { @@ -431,46 +457,53 @@ void processKeyboardHeld() { cameraSpeed /= 2; } - if (player.cursor.breakingProgress == 0) { - if (keyboardState[SDL_SCANCODE_W]) { - // Example: move up - player.rect.y -= cameraSpeed; -// if (player.rect.y < (DISPLAY_HEIGHT / 2)) { -// player.rect.y = (DISPLAY_HEIGHT / 2); -// } - if (player.rect.y < 0) { - player.rect.y = 0; - } - } - if (keyboardState[SDL_SCANCODE_S]) { - player.rect.y += cameraSpeed; -// if (player.rect.y > (MAP_HEIGHT * TILE_SIZE) - (DISPLAY_HEIGHT / 2)) { -// player.rect.y = (MAP_HEIGHT * TILE_SIZE) - (DISPLAY_HEIGHT / 2); -// } - if (player.rect.y > (MAP_HEIGHT * TILE_SIZE)) { - player.rect.y = (MAP_HEIGHT * TILE_SIZE); - } - } - if (keyboardState[SDL_SCANCODE_A]) { - player.rect.x -= cameraSpeed; -// if (player.rect.x < (DISPLAY_WIDTH / 2)) { -// player.rect.x = (DISPLAY_WIDTH / 2); -// } - if (player.rect.x < 0) { - player.rect.x = 0; - } - } - if (keyboardState[SDL_SCANCODE_D]) { - player.rect.x += cameraSpeed; -// if (player.rect.x > (MAP_WIDTH * TILE_SIZE) - (DISPLAY_WIDTH / 2)) { -// player.rect.x = (MAP_WIDTH * TILE_SIZE) - (DISPLAY_WIDTH / 2); -// } - if (player.rect.x > (MAP_WIDTH * TILE_SIZE)) { - player.rect.x = (MAP_WIDTH * TILE_SIZE); - } + if (keyboardState[SDL_SCANCODE_F8]) { + if (player.cursor.targetTile->health) { + player.cursor.targetTile->health--; } } + if (player.cursor.breakingProgress == 0) { + SDL_Rect newRect = player.rect; + + if (keyboardState[SDL_SCANCODE_W]) { + newRect.y -= cameraSpeed; + if (newRect.y >= 0 && canMoveWithRadius(newRect)) { + player.rect = newRect; + } + } + + if (keyboardState[SDL_SCANCODE_S]) { + newRect = player.rect; + newRect.y += cameraSpeed; + if (newRect.y + newRect.h <= MAP_HEIGHT * TILE_SIZE && canMoveWithRadius(newRect)) { + player.rect = newRect; + } + } + + if (keyboardState[SDL_SCANCODE_A]) { + newRect = player.rect; + newRect.x -= cameraSpeed; + if (newRect.x >= 0 && canMoveWithRadius(newRect)) { + player.rect = newRect; + } + } + + if (keyboardState[SDL_SCANCODE_D]) { + newRect = player.rect; + newRect.x += cameraSpeed; + if (newRect.x + newRect.w <= MAP_WIDTH * TILE_SIZE && canMoveWithRadius(newRect)) { + player.rect = newRect; + } + } + + + // Update tileRect only after actual movement + player.tileRect.x = player.rect.x / TILE_SIZE; + player.tileRect.y = player.rect.y / TILE_SIZE; + } + + if (keyboardState[SDL_SCANCODE_F]) { for (int x = playerTileX - 2; x < playerTileX + 2; x++) { if (x < 0) { @@ -587,7 +620,10 @@ int main(__attribute__((unused)) int argc, __attribute__((unused)) char *args[]) running = processEvent(e); } + entities.entities[0].target = player.tileRect; + updateItems(); + updateEntities(); updatePlayer(&player); updateTiles(); animationStep++; @@ -658,7 +694,7 @@ void genInitMap() { if (oreNrm > oreNrmMax) oreNrmMax = oreNrm; // [Same as your original terrain generation logic...] - BackgroundType baseType; + BackgroundType baseType = BGType_COBBLE0; if (terrain < 0.30) { baseType = (humidity < 0.5) ? BGType_WATER_SHALLOW : BGType_WATER_DEEP; } else if (terrain < 0.35) { @@ -694,6 +730,9 @@ void genInitMap() { } } + if (finalType > BGType_END) { + finalType = BGType_COBBLE0; + } backgroundMap[y][x].type = finalType; } } diff --git a/player/player.c b/player/player.c index a9109fc..8c13850 100644 --- a/player/player.c +++ b/player/player.c @@ -74,26 +74,26 @@ inline void adjustRect(SDL_Rect *rect, SDL_Rect playerRect) { // return x > 0 && y > 0 && x < DISPLAY_WIDTH && y < DISPLAY_HEIGHT; //} -//bool isInboundsRect(SDL_Rect rect) { -// if (isInbounds(rect.x, rect.y)) { +//bool isInboundsRect(SDL_Rect tileRect) { +// if (isInbounds(tileRect.x, tileRect.y)) { // return true; // } -// if (rect.x < 0) { -// rect.x += rect.w; +// if (tileRect.x < 0) { +// tileRect.x += tileRect.w; // } -// if (rect.y < 0) { -// rect.y += rect.h; +// if (tileRect.y < 0) { +// tileRect.y += tileRect.h; // } -// if (isInbounds(rect.x, rect.y)) { +// if (isInbounds(tileRect.x, tileRect.y)) { // return true; // } -// if (rect.x > DISPLAY_WIDTH) { -// rect.x -= rect.w; +// if (tileRect.x > DISPLAY_WIDTH) { +// tileRect.x -= tileRect.w; // } -// if (rect.y > DISPLAY_HEIGHT) { -// rect.y -= rect.h; +// if (tileRect.y > DISPLAY_HEIGHT) { +// tileRect.y -= tileRect.h; // } -// return isInbounds(rect.x, rect.y); +// return isInbounds(tileRect.x, tileRect.y); //} void initPlayer(Player *plr) { @@ -163,7 +163,6 @@ void renderPlayer(Player *plr) { SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0); SDL_SetRenderTarget(mainRenderer, entityTexture); - SDL_RenderClear(mainRenderer); SDL_RenderCopy(mainRenderer, atlasTexture, &playerTextureRect, &PlayerRect); //SDL_RenderCopy(mainRenderer, PlayerTexture, NULL, &PlayerRect); SDL_SetRenderTarget(mainRenderer, hudTexture); diff --git a/player/player.h b/player/player.h index bcfc228..5db6dc7 100644 --- a/player/player.h +++ b/player/player.h @@ -18,7 +18,7 @@ extern SDL_Texture *hudTexture; //bool isInbounds(int x, int y); // -//bool isInboundsRect(SDL_Rect rect); +//bool isInboundsRect(SDL_Rect tileRect); // //bool isInboundsTile(int x, int y); @@ -58,6 +58,7 @@ typedef struct Player{ uint8_t prevHealth; uint8_t healthIdle; SDL_Rect rect; + MiniRect tileRect; } Player; void setActivePlayerSlot(Player *plr, ItemType activeSlotIndex); diff --git a/tiles/tile.c b/tiles/tile.c index e28e07c..fec42b1 100644 --- a/tiles/tile.c +++ b/tiles/tile.c @@ -104,10 +104,10 @@ void registerTile(char fname[20], SDL_Renderer *renderer) { NULL, texture, NULL, - createFlippedTexture(renderer, texture, SDL_FLIP_HORIZONTAL), - NULL, createRotatedTexture(renderer, texture, 90), NULL, + createFlippedTexture(renderer, texture, SDL_FLIP_HORIZONTAL), + NULL, createRotatedTexture(renderer, texture, 270) }; @@ -118,6 +118,7 @@ void registerTile(char fname[20], SDL_Renderer *renderer) { } TileRegistry[indexTile].type = indexTile; + TileRegistry[indexTile].maxHealth = 200; TileRegistry[indexTile].animation.frameCount = frame + 1; TileRegistry[indexTile].animation.divisor = 1; @@ -162,18 +163,13 @@ void registerBackgroundTile(char fname[20], SDL_Renderer *renderer) { BackgroundTileRegistry[indexBgTile].type = indexBgTile; BackgroundTileRegistry[indexBgTile].animation.frameCount = frame + 1; BackgroundTileRegistry[indexBgTile].animation.divisor = 1; + BackgroundTileRegistry[indexBgTile].walkable = true; if (indexBgTile + 1 > backgroundTileTypeIndex) { backgroundTileTypeIndex = indexBgTile + 1; } } -int compareStrings(const void *a, const void *b) { - const char *strA = *(const char **) a; - const char *strB = *(const char **) b; - return strcmp(strA, strB); -} - void loadTiles(SDL_Renderer *renderer) { DIR *dir = opendir("./assets/tiles"); if (!dir) { @@ -281,15 +277,19 @@ void setupTiles() { TileRegistry[TYPE_FURNACE].needsTicks = true; TileRegistry[TYPE_FURNACE].animation.divisor = 8; TileRegistry[TYPE_BELT].needsTicks = true; + TileRegistry[TYPE_BELT].walkable = true; TileRegistry[TYPE_MINER].needsTicks = true; TileRegistry[TYPE_MINER].outputLane[MINER_OUTPUT_SLOT] = 1; TileRegistry[TYPE_MINER].startFrame = 1; + TileRegistry[TYPE_AIR].walkable = true; BackgroundTileRegistry[BGType_WATER_DEEP].animation.divisor = 16; BackgroundTileRegistry[BGType_WATER_SHALLOW].animation.divisor = 12; BackgroundTileRegistry[BGType_GRASS_FLOWER0].animation.divisor = 16; BackgroundTileRegistry[BGType_GRASS_FLOWER1].animation.divisor = 16; BackgroundTileRegistry[BGType_GRASS_FLOWER2].animation.divisor = 16; + BackgroundTileRegistry[BGType_WATER_SHALLOW].walkable = false; + BackgroundTileRegistry[BGType_WATER_DEEP].walkable = false; } uint16_t getBreakTime(int type) { @@ -347,14 +347,21 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) { adjustRect(&dstRect, playerRect); BackgroundTile bt = backgroundMap[y][x]; - SDL_Texture *tex = BackgroundTileRegistry[bt.type].animation.textures[animationStep % - BackgroundTileRegistry[bt.type].animation.frameCount]; - SDL_Rect atlRect = BackgroundTileRegistry[bt.type].animation.atlasRects[ - (animationStep / BackgroundTileRegistry[bt.type].animation.divisor) % - BackgroundTileRegistry[bt.type].animation.frameCount]; - if (atlRect.w != 0 && atlRect.h != 0) { - SDL_RenderCopy(renderer, atlasTexture, &atlRect, &dstRect); - //SDL_RenderCopy(renderer, tex, NULL, &dstRect); + if (bt.type > BGType_END) { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + SDL_RenderFillRect(renderer, &dstRect); + printf("Error on tile %d, %d\n", x, y); + backgroundMap[y][x].type = BGType_PLATINUM_ORE; + } else { + SDL_Texture *tex = BackgroundTileRegistry[bt.type].animation.textures[animationStep % + BackgroundTileRegistry[bt.type].animation.frameCount]; + SDL_Rect atlRect = BackgroundTileRegistry[bt.type].animation.atlasRects[ + (animationStep / BackgroundTileRegistry[bt.type].animation.divisor) % + BackgroundTileRegistry[bt.type].animation.frameCount]; + if (atlRect.w != 0 && atlRect.h != 0) { + SDL_RenderCopy(renderer, atlasTexture, &atlRect, &dstRect); + //SDL_RenderCopy(renderer, tex, NULL, &dstRect); + } } } } @@ -398,6 +405,11 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) { if (atlRect.w != 0 && atlRect.h != 0) { //SDL_RenderCopy(renderer, tex, NULL, &dstRect); SDL_RenderCopy(renderer, atlasTexture, &atlRect, &dstRect); +// if (t.health < TileRegistry[t.type].maxHealth) { +// SDL_Color tileHealthColor = {(t.health / TileRegistry[t.type].maxHealth ) * 255, (TileRegistry[t.type].maxHealth / t.health) * 255, 0, 255}; +// renderBar(mainRenderer, x * TILE_SIZE, (y * TILE_SIZE) + (TILE_SIZE / 2), TILE_SIZE, 8, +// TileRegistry[t.type].maxHealth, t.health, tileHealthColor, 4); +// } } } } @@ -437,6 +449,13 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) { SDL_SetRenderTarget(renderer, oldTarget); } +bool isWalkable(MiniRect tileCoords) { + BackgroundTileType bgt = BackgroundTileRegistry[backgroundMap[tileCoords.y][tileCoords.x].type]; + TileTypeReg fgt = TileRegistry[tileMap[tileCoords.y][tileCoords.x].type]; + + return bgt.walkable && fgt.walkable; +} + void updateTiles() { } \ No newline at end of file diff --git a/tiles/tile.h b/tiles/tile.h index a3f4d65..d5166e8 100644 --- a/tiles/tile.h +++ b/tiles/tile.h @@ -8,8 +8,8 @@ #include "../util/util.h" #include "../items/item.h" -#define MAP_WIDTH 1000 -#define MAP_HEIGHT 1000 +#define MAP_WIDTH 500 +#define MAP_HEIGHT 500 #define DISPLAY_MAP_WIDTH 60 #define DISPLAY_MAP_HEIGHT 31 @@ -93,17 +93,19 @@ typedef struct TileTypeReg { bool outputLane[ItemSlotCount]; bool needsTicks; char startFrame; + bool walkable; + uint16_t maxHealth; } TileTypeReg; +bool isWalkable(MiniRect tileCoords); + typedef struct BackgroundTileType { ItemType type; char name[20]; Animation animation; + bool walkable; } BackgroundTileType; -typedef struct BackgroundTile { - BackgroundType type; -} BackgroundTile; #define TILEREGISTRY_SIZE 64 @@ -115,6 +117,20 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect); void updateTiles(); +typedef struct PathFindDat { + int gCost; + int hCost; + struct PathFindDat* parent; + bool closed; + bool open; + uint32_t version; +} PathFindDat; + +typedef struct BackgroundTile { + BackgroundType type; + PathFindDat pathFind; +} BackgroundTile; + typedef struct Tile { OrientDirection direction; ItemType type; @@ -124,6 +140,8 @@ typedef struct Tile { MiniRect rect; int neededUpdateIndex; char fixedFrame; + PathFindDat pathFind; + uint16_t health; } Tile; diff --git a/util/atlas.c b/util/atlas.c index 2d54609..906ed27 100644 --- a/util/atlas.c +++ b/util/atlas.c @@ -33,7 +33,7 @@ void storeRect(SDL_Rect rect) { if (allocatedRectCount < MAX_RECTS) { allocatedRects[allocatedRectCount++] = rect; } else { - fprintf(stderr, "Error: atlas rect limit reached!\n"); + fprintf(stderr, "Error: atlas tileRect limit reached!\n"); } } diff --git a/util/audio.c b/util/audio.c index 5ec9d75..9be5475 100644 --- a/util/audio.c +++ b/util/audio.c @@ -106,7 +106,7 @@ void audio_callback(void *userdata, Uint8 *stream, int len) { float gainL = 1; float gainR = 1; - float targetAmp = (voice->volume / 255.0f); + float targetAmp = (voice->volume / 255.0f) / 2; if (v < NUM_SYNTH_VOICES - MIDI_VOICES) { float distanceAtten = 1.0f - fminf(fabsf(dx) / audio->maxPanDistance, 1.0f); targetAmp *= distanceAtten; diff --git a/util/pathfinding.c b/util/pathfinding.c new file mode 100644 index 0000000..8a5073e --- /dev/null +++ b/util/pathfinding.c @@ -0,0 +1,128 @@ +// +// Created by bruno on 7.6.2025. +// + +#include "pathfinding.h" + +uint32_t globalPathfindingVersion = 1; + +Node openList[MAX_OPEN_NODES]; +int openCount = 0; + + +void clear_pathfind_data() { + for (int y = 0; y < MAP_HEIGHT; ++y) + for (int x = 0; x < MAP_WIDTH; ++x) + tileMap[y][x].pathFind = (PathFindDat) {0}; +} + +int heuristic(MiniRect a, MiniRect b) { + return abs(a.x - b.x) + abs(a.y - b.y); +} + + +void add_to_open(MiniRect pos, int fCost) { + if (openCount < MAX_OPEN_NODES) + openList[openCount++] = (Node) {pos.x, pos.y, fCost}; +} + +Node pop_best_node() { + int bestIdx = 0; + for (int i = 1; i < openCount; ++i) { + if (openList[i].fCost < openList[bestIdx].fCost) + bestIdx = i; + } + + Node best = openList[bestIdx]; + openList[bestIdx] = openList[--openCount]; + return best; +} + +bool find_path(MiniRect start, MiniRect end) { + clear_pathfind_data(); + openCount = 0; + + Tile *startT = &tileMap[start.y][start.x]; + startT->pathFind.gCost = 0; + startT->pathFind.hCost = heuristic(start, end); + startT->pathFind.open = true; + + add_to_open(start, startT->pathFind.gCost + startT->pathFind.hCost); + + const int dirs[4][2] = { + {0, -1}, + {1, 0}, + {0, 1}, + {-1, 0} + }; + + while (openCount > 0) { + Node current = pop_best_node(); + PathFindDat *currentPath = &tileMap[current.pos.y][current.pos.x].pathFind; + currentPath->open = false; + currentPath->closed = true; + + if (memcmp(¤t.pos, &end, sizeof(MiniRect)) == 0) { + return true; // Path found! + } + + for (int i = 0; i < 4; ++i) { + MiniRect n = {current.pos.x + dirs[i][0], current.pos.y + dirs[i][1]}; + + if (!isWalkable(n)) continue; + + PathFindDat *neighbor = &tileMap[n.y][n.x].pathFind; + + if (neighbor->closed) continue; + + int tentativeG = currentPath->gCost + 1; + + if (!neighbor->open || tentativeG < neighbor->gCost) { + neighbor->gCost = tentativeG; + neighbor->hCost = heuristic(n, end); + neighbor->parent = currentPath; + + if (!neighbor->open) { + neighbor->open = true; + add_to_open(n, neighbor->gCost + neighbor->hCost); + } + } + } + } + + return false; // No path found +} + +Path reconstruct_path(MiniRect end) { + Path path = { .length = 0 }; + + PathFindDat* current = &tileMap[end.y][end.x].pathFind; + + // Walk back through the parent pointers + while (current != NULL) { + // Find the tile coordinates for the current node + for (int y = 0; y < MAP_HEIGHT; ++y) { + for (int x = 0; x < MAP_WIDTH; ++x) { + if (&tileMap[y][x].pathFind == current) { + if (path.length < MAX_OPEN_NODES) { + path.steps[path.length++] = (MiniRect){ x, y }; + } + goto found; + } + } + } + found: + current = current->parent; + } + + // Reverse the path to go from start to end + for (int i = 0; i < path.length / 2; ++i) { + MiniRect tmp = path.steps[i]; + path.steps[i] = path.steps[path.length - i - 1]; + path.steps[path.length - i - 1] = tmp; + } + path.stepIndex = 0; + + return path; +} + diff --git a/util/pathfinding.h b/util/pathfinding.h new file mode 100644 index 0000000..c06f1a1 --- /dev/null +++ b/util/pathfinding.h @@ -0,0 +1,42 @@ +// +// Created by bruno on 7.6.2025. +// + +#ifndef FACTORYGAME_PATHFINDING_H +#define FACTORYGAME_PATHFINDING_H + +#include +#include "../tiles/tile.h" + +//extern uint32_t globalPathfindingVersion; +//#define MAX_OPEN_NODES (MAP_WIDTH * MAP_HEIGHT) +#define MAX_OPEN_NODES 100 + +typedef struct Node { + MiniRect pos; + int fCost; +} Node; + +typedef struct { + MiniRect steps[MAX_OPEN_NODES]; + int stepIndex; + int length; +} Path; + + +extern Node openList[MAX_OPEN_NODES]; +extern int openCount; + +int heuristic(MiniRect a, MiniRect b); + +Path reconstruct_path(MiniRect end); + +bool find_path(MiniRect start, MiniRect end); + +Node pop_best_node(); + +void add_to_open(MiniRect pos, int fCost); + +void clear_pathfind_data(); + +#endif //FACTORYGAME_PATHFINDING_H diff --git a/util/util.c b/util/util.c index f6c3ba7..2d48927 100644 --- a/util/util.c +++ b/util/util.c @@ -4,6 +4,8 @@ #include #include "util.h" +#include "../tiles/tile.h" +#include "font.h" //#include "font.h" @@ -132,7 +134,7 @@ void renderBar(SDL_Renderer *renderer, char barString[20]; sprintf(barString, "%d/%d", currentValue, maxValue); - //renderText(mainRenderer, fonts[3], barString, width / 2, margin); + renderText(mainRenderer, fonts[3], barString, width / 2, margin); } int cmpstringp(const void *p1, const void *p2) { @@ -178,4 +180,109 @@ void iterateSortedDir(const char *path, DirEntryCallback callback, SDL_Renderer } free(names); } +} + +bool checkCollision(SDL_Rect a, SDL_Rect b) { + //The sides of the rectangles + int leftA, leftB; + int rightA, rightB; + int topA, topB; + int bottomA, bottomB; + + //Calculate the sides of tileRect A + leftA = a.x; + rightA = a.x + a.w; + topA = a.y; + bottomA = a.y + a.h; + + //Calculate the sides of tileRect B + leftB = b.x; + rightB = b.x + b.w; + topB = b.y; + bottomB = b.y + b.h; + + //If any of the sides from A are outside of B + if (bottomA <= topB) { + return false; + } + + if (topA >= bottomB) { + return false; + } + + if (rightA <= leftB) { + return false; + } + + if (leftA >= rightB) { + return false; + } + + //If none of the sides from A are outside B + return true; +} + +bool canMoveTo(SDL_Rect newRect) { + // Round down to get all tiles the rect overlaps + int left = newRect.x / TILE_SIZE; + int right = (newRect.x + newRect.w - 1) / TILE_SIZE; + int top = newRect.y / TILE_SIZE; + int bottom = (newRect.y + newRect.h - 1) / TILE_SIZE; + + for (int tx = left; tx <= right; ++tx) { + for (int ty = top; ty <= bottom; ++ty) { + MiniRect tile = { tx, ty }; + if (!isWalkable(tile)) { + return false; + } + } + } + return true; +} + +bool canMoveWithRadius(SDL_Rect centerRect) { + // 16px radius — create a square bounding box around center + int radius = 16; + + int left = (centerRect.x - radius) / TILE_SIZE; + int right = (centerRect.x + radius - 1) / TILE_SIZE; + int top = (centerRect.y - radius) / TILE_SIZE; + int bottom = (centerRect.y + radius - 1) / TILE_SIZE; + + int x, y; + for (x = left; x <= right; x++) { + for (y = top; y <= bottom; y++) { + MiniRect tile; + tile.x = x; + tile.y = y; + + if (!isWalkable(tile)) { + // Get pixel bounds of tile + int tileLeft = x * TILE_SIZE; + int tileRight = tileLeft + TILE_SIZE; + int tileTop = y * TILE_SIZE; + int tileBottom = tileTop + TILE_SIZE; + + // Bounding box of the player + int playerLeft = centerRect.x - radius; + int playerRight = centerRect.x + radius; + int playerTop = centerRect.y - radius; + int playerBottom = centerRect.y + radius; + + // AABB collision check + if (playerRight > tileLeft && playerLeft < tileRight && + playerBottom > tileTop && playerTop < tileBottom) { + return 0; // Collision + } + } + } + } + + return 1; // No collisions +} + +int compareStrings(const void *a, const void *b) { + const char *strA = *(const char **) a; + const char *strB = *(const char **) b; + return strcmp(strA, strB); } \ No newline at end of file diff --git a/util/util.h b/util/util.h index 87bdcae..2b7543e 100644 --- a/util/util.h +++ b/util/util.h @@ -72,4 +72,12 @@ void renderBar(SDL_Renderer *renderer, int maxValue, int currentValue, SDL_Color barColor, int margin); -#endif //FACTORYGAME_UTIL_H +bool checkCollision(SDL_Rect a, SDL_Rect b); + +int compareStrings(const void *a, const void *b); + +bool canMoveTo(SDL_Rect newRect); + +bool canMoveWithRadius(SDL_Rect centerRect); + +#endif //FACTORYGAME_UTIL_H \ No newline at end of file