This commit is contained in:
2025-06-08 17:22:30 +02:00
parent 64cac7578d
commit 79c8b747cd
16 changed files with 686 additions and 79 deletions

View File

@@ -45,6 +45,10 @@ set(SOURCE_FILES
util/atlas.h util/atlas.h
tiles/miner.c tiles/miner.c
tiles/miner.h tiles/miner.h
util/pathfinding.c
util/pathfinding.h
entity/entity.c
entity/entity.h
) )
add_executable(factorygame ${SOURCE_FILES}) add_executable(factorygame ${SOURCE_FILES})

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

187
entity/entity.c Normal file
View File

@@ -0,0 +1,187 @@
//
// Created by bruno on 7.6.2025.
//
#include <SDL2/SDL.h>
#include <dirent.h>
#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
}

55
entity/entity.h Normal file
View File

@@ -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

View File

@@ -246,7 +246,7 @@ void renderItem(ItemOnBelt item, SDL_Renderer *renderer, int lane, SDL_Rect play
xOffset += perpX * laneSign * laneOffset; xOffset += perpX * laneSign * laneOffset;
yOffset += perpY * laneSign * laneOffset; yOffset += perpY * laneSign * laneOffset;
//--- 5) apply to rect //--- 5) apply to tileRect
rect.x += (int) roundf(xOffset); rect.x += (int) roundf(xOffset);
rect.y += (int) roundf(yOffset); rect.y += (int) roundf(yOffset);
@@ -273,7 +273,7 @@ void renderItem(ItemOnBelt item, SDL_Renderer *renderer, int lane, SDL_Rect play
adjustRect(&rectA, playerRect); adjustRect(&rectA, playerRect);
SDL_RenderFillRect(renderer, &rectA); 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, SDL_RenderCopy(renderer, atlasTexture,
&ItemRegistry[item.type].beltAnimation.atlasRects[ORIENT_LEFT][ &ItemRegistry[item.type].beltAnimation.atlasRects[ORIENT_LEFT][
(animationStep / ItemRegistry[item.type].beltAnimation.divisor) % (animationStep / ItemRegistry[item.type].beltAnimation.divisor) %

115
main.c
View File

@@ -9,6 +9,7 @@
#include "player/player.h" #include "player/player.h"
#include "util/perlin.h" #include "util/perlin.h"
#include "util/atlas.h" #include "util/atlas.h"
#include "entity/entity.h"
typedef struct GameState { typedef struct GameState {
Player player; Player player;
@@ -16,6 +17,9 @@ typedef struct GameState {
BackgroundTile backgroundTileMap[MAP_HEIGHT][MAP_WIDTH]; BackgroundTile backgroundTileMap[MAP_HEIGHT][MAP_WIDTH];
AudioData audioData; AudioData audioData;
TileArray neededUpdates; TileArray neededUpdates;
EntityArray entities;
Node openList[MAX_OPEN_NODES];
int openCount;
} GameState; } GameState;
GameState gameState; GameState gameState;
@@ -40,6 +44,9 @@ int loadGameState(char *filename, Player *plr) {
audioData.playerRect = tmp; audioData.playerRect = tmp;
audioData.totalSamples = 0; audioData.totalSamples = 0;
memcpy(&neededUpdates, &gameState.neededUpdates, sizeof(gameState.neededUpdates)); 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.targetTile = NULL;
plr->cursor.prevTargetTile = NULL; plr->cursor.prevTargetTile = NULL;
return 0; return 0;
@@ -53,6 +60,9 @@ void saveGameState(char *filename, Player *plr) {
memcpy(gameState.backgroundTileMap, backgroundMap, sizeof(gameState.backgroundTileMap)); memcpy(gameState.backgroundTileMap, backgroundMap, sizeof(gameState.backgroundTileMap));
memcpy(&gameState.audioData, &audioData, sizeof(gameState.audioData)); memcpy(&gameState.audioData, &audioData, sizeof(gameState.audioData));
memcpy(&gameState.neededUpdates, &neededUpdates, sizeof(neededUpdates)); 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"); FILE *gameSave = fopen(filename, "wb");
if (!gameSave) { if (!gameSave) {
@@ -142,6 +152,20 @@ int init() {
loadBackgroundTiles(mainRenderer); loadBackgroundTiles(mainRenderer);
loadTiles(mainRenderer); loadTiles(mainRenderer);
loadItems(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(); setupTiles();
// for (ItemType i = 0; i < ITEMREGISTRY_SIZE; i++) { // for (ItemType i = 0; i < ITEMREGISTRY_SIZE; i++) {
@@ -226,6 +250,7 @@ int render() {
rect2.h = ATLAS_SIZE; rect2.h = ATLAS_SIZE;
renderAllTiles(mainRenderer, player.rect); renderAllTiles(mainRenderer, player.rect);
renderEntities(mainRenderer, player.rect);
renderPlayer(&player); renderPlayer(&player);
@@ -333,6 +358,7 @@ void processMousePosition() {
if (player.inventory.slotCounts[player.inventory.activeSlotIndex] > 0) { if (player.inventory.slotCounts[player.inventory.activeSlotIndex] > 0) {
player.inventory.slotCounts[player.inventory.activeSlotIndex]--; player.inventory.slotCounts[player.inventory.activeSlotIndex]--;
player.cursor.targetTile->type = 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.x = player.cursor.tileX;
player.cursor.targetTile->rect.y = player.cursor.tileY; player.cursor.targetTile->rect.y = player.cursor.tileY;
if (TileRegistry[player.inventory.activeSlotIndex].needsTicks) { if (TileRegistry[player.inventory.activeSlotIndex].needsTicks) {
@@ -431,46 +457,53 @@ void processKeyboardHeld() {
cameraSpeed /= 2; cameraSpeed /= 2;
} }
if (player.cursor.breakingProgress == 0) { if (keyboardState[SDL_SCANCODE_F8]) {
if (keyboardState[SDL_SCANCODE_W]) { if (player.cursor.targetTile->health) {
// Example: move up player.cursor.targetTile->health--;
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 (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]) { if (keyboardState[SDL_SCANCODE_F]) {
for (int x = playerTileX - 2; x < playerTileX + 2; x++) { for (int x = playerTileX - 2; x < playerTileX + 2; x++) {
if (x < 0) { if (x < 0) {
@@ -587,7 +620,10 @@ int main(__attribute__((unused)) int argc, __attribute__((unused)) char *args[])
running = processEvent(e); running = processEvent(e);
} }
entities.entities[0].target = player.tileRect;
updateItems(); updateItems();
updateEntities();
updatePlayer(&player); updatePlayer(&player);
updateTiles(); updateTiles();
animationStep++; animationStep++;
@@ -658,7 +694,7 @@ void genInitMap() {
if (oreNrm > oreNrmMax) oreNrmMax = oreNrm; if (oreNrm > oreNrmMax) oreNrmMax = oreNrm;
// [Same as your original terrain generation logic...] // [Same as your original terrain generation logic...]
BackgroundType baseType; BackgroundType baseType = BGType_COBBLE0;
if (terrain < 0.30) { if (terrain < 0.30) {
baseType = (humidity < 0.5) ? BGType_WATER_SHALLOW : BGType_WATER_DEEP; baseType = (humidity < 0.5) ? BGType_WATER_SHALLOW : BGType_WATER_DEEP;
} else if (terrain < 0.35) { } else if (terrain < 0.35) {
@@ -694,6 +730,9 @@ void genInitMap() {
} }
} }
if (finalType > BGType_END) {
finalType = BGType_COBBLE0;
}
backgroundMap[y][x].type = finalType; backgroundMap[y][x].type = finalType;
} }
} }

View File

@@ -74,26 +74,26 @@ inline void adjustRect(SDL_Rect *rect, SDL_Rect playerRect) {
// return x > 0 && y > 0 && x < DISPLAY_WIDTH && y < DISPLAY_HEIGHT; // return x > 0 && y > 0 && x < DISPLAY_WIDTH && y < DISPLAY_HEIGHT;
//} //}
//bool isInboundsRect(SDL_Rect rect) { //bool isInboundsRect(SDL_Rect tileRect) {
// if (isInbounds(rect.x, rect.y)) { // if (isInbounds(tileRect.x, tileRect.y)) {
// return true; // return true;
// } // }
// if (rect.x < 0) { // if (tileRect.x < 0) {
// rect.x += rect.w; // tileRect.x += tileRect.w;
// } // }
// if (rect.y < 0) { // if (tileRect.y < 0) {
// rect.y += rect.h; // tileRect.y += tileRect.h;
// } // }
// if (isInbounds(rect.x, rect.y)) { // if (isInbounds(tileRect.x, tileRect.y)) {
// return true; // return true;
// } // }
// if (rect.x > DISPLAY_WIDTH) { // if (tileRect.x > DISPLAY_WIDTH) {
// rect.x -= rect.w; // tileRect.x -= tileRect.w;
// } // }
// if (rect.y > DISPLAY_HEIGHT) { // if (tileRect.y > DISPLAY_HEIGHT) {
// rect.y -= rect.h; // tileRect.y -= tileRect.h;
// } // }
// return isInbounds(rect.x, rect.y); // return isInbounds(tileRect.x, tileRect.y);
//} //}
void initPlayer(Player *plr) { void initPlayer(Player *plr) {
@@ -163,7 +163,6 @@ void renderPlayer(Player *plr) {
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0); SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0);
SDL_SetRenderTarget(mainRenderer, entityTexture); SDL_SetRenderTarget(mainRenderer, entityTexture);
SDL_RenderClear(mainRenderer);
SDL_RenderCopy(mainRenderer, atlasTexture, &playerTextureRect, &PlayerRect); SDL_RenderCopy(mainRenderer, atlasTexture, &playerTextureRect, &PlayerRect);
//SDL_RenderCopy(mainRenderer, PlayerTexture, NULL, &PlayerRect); //SDL_RenderCopy(mainRenderer, PlayerTexture, NULL, &PlayerRect);
SDL_SetRenderTarget(mainRenderer, hudTexture); SDL_SetRenderTarget(mainRenderer, hudTexture);

View File

@@ -18,7 +18,7 @@ extern SDL_Texture *hudTexture;
//bool isInbounds(int x, int y); //bool isInbounds(int x, int y);
// //
//bool isInboundsRect(SDL_Rect rect); //bool isInboundsRect(SDL_Rect tileRect);
// //
//bool isInboundsTile(int x, int y); //bool isInboundsTile(int x, int y);
@@ -58,6 +58,7 @@ typedef struct Player{
uint8_t prevHealth; uint8_t prevHealth;
uint8_t healthIdle; uint8_t healthIdle;
SDL_Rect rect; SDL_Rect rect;
MiniRect tileRect;
} Player; } Player;
void setActivePlayerSlot(Player *plr, ItemType activeSlotIndex); void setActivePlayerSlot(Player *plr, ItemType activeSlotIndex);

View File

@@ -104,10 +104,10 @@ void registerTile(char fname[20], SDL_Renderer *renderer) {
NULL, NULL,
texture, texture,
NULL, NULL,
createFlippedTexture(renderer, texture, SDL_FLIP_HORIZONTAL),
NULL,
createRotatedTexture(renderer, texture, 90), createRotatedTexture(renderer, texture, 90),
NULL, NULL,
createFlippedTexture(renderer, texture, SDL_FLIP_HORIZONTAL),
NULL,
createRotatedTexture(renderer, texture, 270) createRotatedTexture(renderer, texture, 270)
}; };
@@ -118,6 +118,7 @@ void registerTile(char fname[20], SDL_Renderer *renderer) {
} }
TileRegistry[indexTile].type = indexTile; TileRegistry[indexTile].type = indexTile;
TileRegistry[indexTile].maxHealth = 200;
TileRegistry[indexTile].animation.frameCount = frame + 1; TileRegistry[indexTile].animation.frameCount = frame + 1;
TileRegistry[indexTile].animation.divisor = 1; TileRegistry[indexTile].animation.divisor = 1;
@@ -162,18 +163,13 @@ void registerBackgroundTile(char fname[20], SDL_Renderer *renderer) {
BackgroundTileRegistry[indexBgTile].type = indexBgTile; BackgroundTileRegistry[indexBgTile].type = indexBgTile;
BackgroundTileRegistry[indexBgTile].animation.frameCount = frame + 1; BackgroundTileRegistry[indexBgTile].animation.frameCount = frame + 1;
BackgroundTileRegistry[indexBgTile].animation.divisor = 1; BackgroundTileRegistry[indexBgTile].animation.divisor = 1;
BackgroundTileRegistry[indexBgTile].walkable = true;
if (indexBgTile + 1 > backgroundTileTypeIndex) { if (indexBgTile + 1 > backgroundTileTypeIndex) {
backgroundTileTypeIndex = indexBgTile + 1; 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) { void loadTiles(SDL_Renderer *renderer) {
DIR *dir = opendir("./assets/tiles"); DIR *dir = opendir("./assets/tiles");
if (!dir) { if (!dir) {
@@ -281,15 +277,19 @@ void setupTiles() {
TileRegistry[TYPE_FURNACE].needsTicks = true; TileRegistry[TYPE_FURNACE].needsTicks = true;
TileRegistry[TYPE_FURNACE].animation.divisor = 8; TileRegistry[TYPE_FURNACE].animation.divisor = 8;
TileRegistry[TYPE_BELT].needsTicks = true; TileRegistry[TYPE_BELT].needsTicks = true;
TileRegistry[TYPE_BELT].walkable = true;
TileRegistry[TYPE_MINER].needsTicks = true; TileRegistry[TYPE_MINER].needsTicks = true;
TileRegistry[TYPE_MINER].outputLane[MINER_OUTPUT_SLOT] = 1; TileRegistry[TYPE_MINER].outputLane[MINER_OUTPUT_SLOT] = 1;
TileRegistry[TYPE_MINER].startFrame = 1; TileRegistry[TYPE_MINER].startFrame = 1;
TileRegistry[TYPE_AIR].walkable = true;
BackgroundTileRegistry[BGType_WATER_DEEP].animation.divisor = 16; BackgroundTileRegistry[BGType_WATER_DEEP].animation.divisor = 16;
BackgroundTileRegistry[BGType_WATER_SHALLOW].animation.divisor = 12; BackgroundTileRegistry[BGType_WATER_SHALLOW].animation.divisor = 12;
BackgroundTileRegistry[BGType_GRASS_FLOWER0].animation.divisor = 16; BackgroundTileRegistry[BGType_GRASS_FLOWER0].animation.divisor = 16;
BackgroundTileRegistry[BGType_GRASS_FLOWER1].animation.divisor = 16; BackgroundTileRegistry[BGType_GRASS_FLOWER1].animation.divisor = 16;
BackgroundTileRegistry[BGType_GRASS_FLOWER2].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) { uint16_t getBreakTime(int type) {
@@ -347,14 +347,21 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) {
adjustRect(&dstRect, playerRect); adjustRect(&dstRect, playerRect);
BackgroundTile bt = backgroundMap[y][x]; BackgroundTile bt = backgroundMap[y][x];
SDL_Texture *tex = BackgroundTileRegistry[bt.type].animation.textures[animationStep % if (bt.type > BGType_END) {
BackgroundTileRegistry[bt.type].animation.frameCount]; SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
SDL_Rect atlRect = BackgroundTileRegistry[bt.type].animation.atlasRects[ SDL_RenderFillRect(renderer, &dstRect);
(animationStep / BackgroundTileRegistry[bt.type].animation.divisor) % printf("Error on tile %d, %d\n", x, y);
BackgroundTileRegistry[bt.type].animation.frameCount]; backgroundMap[y][x].type = BGType_PLATINUM_ORE;
if (atlRect.w != 0 && atlRect.h != 0) { } else {
SDL_RenderCopy(renderer, atlasTexture, &atlRect, &dstRect); SDL_Texture *tex = BackgroundTileRegistry[bt.type].animation.textures[animationStep %
//SDL_RenderCopy(renderer, tex, NULL, &dstRect); 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) { if (atlRect.w != 0 && atlRect.h != 0) {
//SDL_RenderCopy(renderer, tex, NULL, &dstRect); //SDL_RenderCopy(renderer, tex, NULL, &dstRect);
SDL_RenderCopy(renderer, atlasTexture, &atlRect, &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); 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() { void updateTiles() {
} }

View File

@@ -8,8 +8,8 @@
#include "../util/util.h" #include "../util/util.h"
#include "../items/item.h" #include "../items/item.h"
#define MAP_WIDTH 1000 #define MAP_WIDTH 500
#define MAP_HEIGHT 1000 #define MAP_HEIGHT 500
#define DISPLAY_MAP_WIDTH 60 #define DISPLAY_MAP_WIDTH 60
#define DISPLAY_MAP_HEIGHT 31 #define DISPLAY_MAP_HEIGHT 31
@@ -93,17 +93,19 @@ typedef struct TileTypeReg {
bool outputLane[ItemSlotCount]; bool outputLane[ItemSlotCount];
bool needsTicks; bool needsTicks;
char startFrame; char startFrame;
bool walkable;
uint16_t maxHealth;
} TileTypeReg; } TileTypeReg;
bool isWalkable(MiniRect tileCoords);
typedef struct BackgroundTileType { typedef struct BackgroundTileType {
ItemType type; ItemType type;
char name[20]; char name[20];
Animation animation; Animation animation;
bool walkable;
} BackgroundTileType; } BackgroundTileType;
typedef struct BackgroundTile {
BackgroundType type;
} BackgroundTile;
#define TILEREGISTRY_SIZE 64 #define TILEREGISTRY_SIZE 64
@@ -115,6 +117,20 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect);
void updateTiles(); 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 { typedef struct Tile {
OrientDirection direction; OrientDirection direction;
ItemType type; ItemType type;
@@ -124,6 +140,8 @@ typedef struct Tile {
MiniRect rect; MiniRect rect;
int neededUpdateIndex; int neededUpdateIndex;
char fixedFrame; char fixedFrame;
PathFindDat pathFind;
uint16_t health;
} Tile; } Tile;

View File

@@ -33,7 +33,7 @@ void storeRect(SDL_Rect rect) {
if (allocatedRectCount < MAX_RECTS) { if (allocatedRectCount < MAX_RECTS) {
allocatedRects[allocatedRectCount++] = rect; allocatedRects[allocatedRectCount++] = rect;
} else { } else {
fprintf(stderr, "Error: atlas rect limit reached!\n"); fprintf(stderr, "Error: atlas tileRect limit reached!\n");
} }
} }

View File

@@ -106,7 +106,7 @@ void audio_callback(void *userdata, Uint8 *stream, int len) {
float gainL = 1; float gainL = 1;
float gainR = 1; float gainR = 1;
float targetAmp = (voice->volume / 255.0f); float targetAmp = (voice->volume / 255.0f) / 2;
if (v < NUM_SYNTH_VOICES - MIDI_VOICES) { if (v < NUM_SYNTH_VOICES - MIDI_VOICES) {
float distanceAtten = 1.0f - fminf(fabsf(dx) / audio->maxPanDistance, 1.0f); float distanceAtten = 1.0f - fminf(fabsf(dx) / audio->maxPanDistance, 1.0f);
targetAmp *= distanceAtten; targetAmp *= distanceAtten;

128
util/pathfinding.c Normal file
View File

@@ -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(&current.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;
}

42
util/pathfinding.h Normal file
View File

@@ -0,0 +1,42 @@
//
// Created by bruno on 7.6.2025.
//
#ifndef FACTORYGAME_PATHFINDING_H
#define FACTORYGAME_PATHFINDING_H
#include <stdint.h>
#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

View File

@@ -4,6 +4,8 @@
#include <dirent.h> #include <dirent.h>
#include "util.h" #include "util.h"
#include "../tiles/tile.h"
#include "font.h"
//#include "font.h" //#include "font.h"
@@ -132,7 +134,7 @@ void renderBar(SDL_Renderer *renderer,
char barString[20]; char barString[20];
sprintf(barString, "%d/%d", currentValue, maxValue); 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) { int cmpstringp(const void *p1, const void *p2) {
@@ -179,3 +181,108 @@ void iterateSortedDir(const char *path, DirEntryCallback callback, SDL_Renderer
free(names); 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);
}

View File

@@ -72,4 +72,12 @@ void renderBar(SDL_Renderer *renderer,
int maxValue, int currentValue, int maxValue, int currentValue,
SDL_Color barColor, int margin); SDL_Color barColor, int margin);
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 #endif //FACTORYGAME_UTIL_H