diff --git a/CMakeLists.txt b/CMakeLists.txt index f9556c9..a5d8ee8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,8 @@ set(SOURCE_FILES util/pathfinding.h entity/entity.c entity/entity.h + tiles/turret.c + tiles/turret.h ) add_executable(factorygame ${SOURCE_FILES}) diff --git a/assets/tiles/05_turret_00.png b/assets/tiles/05_turret_00.png new file mode 100644 index 0000000..af1f004 Binary files /dev/null and b/assets/tiles/05_turret_00.png differ diff --git a/assets/tiles/05_turret_01.png b/assets/tiles/05_turret_01.png new file mode 100644 index 0000000..d191068 Binary files /dev/null and b/assets/tiles/05_turret_01.png differ diff --git a/assets/tiles/05_turret_02.png b/assets/tiles/05_turret_02.png new file mode 100644 index 0000000..c31d987 Binary files /dev/null and b/assets/tiles/05_turret_02.png differ diff --git a/assets/tiles/05_turret_03.png b/assets/tiles/05_turret_03.png new file mode 100644 index 0000000..42db6b6 Binary files /dev/null and b/assets/tiles/05_turret_03.png differ diff --git a/assets/tiles/05_turret_04.png b/assets/tiles/05_turret_04.png new file mode 100644 index 0000000..565bc37 Binary files /dev/null and b/assets/tiles/05_turret_04.png differ diff --git a/entity/entity.c b/entity/entity.c index 890c19c..ba5d3e1 100644 --- a/entity/entity.c +++ b/entity/entity.c @@ -7,6 +7,7 @@ #include "entity.h" #include "../player/player.h" #include "../util/pathfinding.h" +#include "../util/font.h" EntityArray entities; @@ -26,40 +27,52 @@ void renderEntities(SDL_Renderer *renderer, SDL_Rect playerRect) { 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[2], healthStr, renderRect.x, renderRect.y); } 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 + 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 needsPath = ent->path.length == 0 || ent->path.stepIndex >= ent->path.length; + bool atTarget = ent->tileRect.x == ent->target.x && + ent->tileRect.y == ent->target.y; - 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; - } + // 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: If it's time to move to the next tile + // Step 2: Movement if (ent->path.length > 0 && ent->path.stepIndex < ent->path.length && animationStep >= ent->entityNextTick) { ent->fromTile = ent->tileRect; @@ -70,7 +83,7 @@ void updateEntities() { ent->path.stepIndex++; } - // Step 3: Interpolate renderRect between fromTile and toTile + // Step 3: Interpolation MiniRect from = { .x = ent->fromTile.x * TILE_SIZE, .y = ent->fromTile.y * TILE_SIZE @@ -93,6 +106,7 @@ void updateEntities() { + void registerEntity(char fname[20], SDL_Renderer *renderer) { char name[21]; @@ -117,7 +131,7 @@ void registerEntity(char fname[20], SDL_Renderer *renderer) { SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); - printf("Ent %s to %d\n", fname, indexEntity); + //printf("Ent %s to %d\n", fname, indexEntity); EntityRegistry[indexEntity].animation.textures[frame] = texture; EntityRegistry[indexEntity].animation.atlasRects[frame] = allocate_32x32(texture, renderer); @@ -126,6 +140,7 @@ void registerEntity(char fname[20], SDL_Renderer *renderer) { 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; diff --git a/entity/entity.h b/entity/entity.h index 220ff40..b99a4af 100644 --- a/entity/entity.h +++ b/entity/entity.h @@ -20,13 +20,14 @@ typedef struct EntityTypeReg { char name[20]; int speed; int entityTickRate; + uint16_t maxHealth; } EntityTypeReg; typedef struct Entity { MiniRect tileRect; SDL_Rect renderRect; EntityType type; - uint16_t health; + int16_t health; MiniRect target; Path path; int entityNextTick; diff --git a/items/item.c b/items/item.c index 79fd339..868cf9a 100644 --- a/items/item.c +++ b/items/item.c @@ -20,6 +20,28 @@ const int dirDx[8] = {-1, -1, -1, 0, 1, 1, 1, 0}; const int dirDy[8] = {1, 0, -1, -1, -1, 0, 1, 1}; const float epsilon = 0.999f; // if we can't move, back it off just below 1 + +bool putOntoNext(ItemOnBelt *itm, int nx, int ny, Tile *next, TileTypeReg *ntt, int newLane) { + if (next->items[newLane].type == 0 && (*ntt).allowedInItems[newLane][itm->type]) { + // MOVE it + ItemOnBelt moved = *itm; + moved.tileX = nx; + moved.tileY = ny; + if (!(*ntt).itemMoves) { + moved.offset = 0.5f; + } + next->items[newLane] = moved; + + // clear this one + itm->type = 0; + return true; + } else { + // both slots full → wait at end + itm->offset = epsilon; + return false; + } +} + void updateItems() { for (int i = 0; i < neededUpdates.activeCount; i++) { @@ -89,25 +111,13 @@ void updateItems() { } - if (next->items[newLane].type == 0 && ntt.allowedInItems[newLane][itm->type]) { - // MOVE it - ItemOnBelt moved = *itm; - moved.tileX = nx; - moved.tileY = ny; - if (!ntt.itemMoves) { - moved.offset = 0.5f; + if (!putOntoNext(itm, nx, ny, next, &ntt, newLane) && next->type != TYPE_BELT) { + for (uint8_t nLane = 0; nLane < ItemSlotCount; nLane++) { + if (putOntoNext(itm, nx, ny, next, &ntt, nLane)) { + break; + } } - next->items[newLane] = moved; - - // clear this one - itm->type = 0; - } else { - // both slots full → wait at end - itm->offset = epsilon; } - - } else { - } } const UpdateTileCallback cb = ItemTileCallbacks[t->type]; diff --git a/items/item.h b/items/item.h index ad4892f..39340aa 100644 --- a/items/item.h +++ b/items/item.h @@ -19,6 +19,7 @@ typedef enum ItemType { TYPE_BELT, TYPE_FURNACE, TYPE_MINER, + TYPE_TURRET, IRON_ORE = ITEMREGISTRY_SIZE / 2, SILVER_ORE, GOLD_ORE, diff --git a/main.c b/main.c index 8884567..d353747 100644 --- a/main.c +++ b/main.c @@ -24,7 +24,6 @@ typedef struct GameState { GameState gameState; int loadGameState(char *filename, Player *plr) { - printf("hello from load\n"); fflush(stdout); FILE *gameSave = fopen(filename, "rb"); if (gameSave) { @@ -154,18 +153,6 @@ int init() { 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++) { @@ -312,6 +299,25 @@ int processEvent(SDL_Event e) { case SDLK_F10: renderAtlas = !renderAtlas; break; + case SDLK_F11: + printf("Enemy is at tile X:%d, Y:%d\n", entities.entities[0].tileRect.x, + entities.entities[0].tileRect.y); + break; + + case SDLK_F2: + Entity entTest; + memset(&entTest, 0, sizeof(Entity)); + entTest.tileRect = player.tileRect; + entTest.renderRect.w = TILE_SIZE; + entTest.renderRect.h = TILE_SIZE; + entTest.target.x = -1; + entTest.target.y = -1; + entTest.health = 100; + entTest.type = GHOST; + entTest.health = 100; + add_entity(&entities, entTest); + break; + case SDLK_F4: Tile *tile = &tileMap[playerTileY][playerTileX]; break; @@ -620,8 +626,28 @@ int main(__attribute__((unused)) int argc, __attribute__((unused)) char *args[]) running = processEvent(e); } - entities.entities[0].target = player.tileRect; - + if (animationStep % 60 == 0) { + for (int i = 0; i < entities.activeCount; i++) { + int x = player.tileRect.x; + int y = player.tileRect.y; + x += (rand() % 10) - 5; + y += (rand() % 10) - 5; + if (x < 0) { + x = 0; + } + if (y < 0) { + y = 0; + } + if (x >= MAP_WIDTH) { + x = MAP_WIDTH - 1; + } + if (y >= MAP_HEIGHT) { + y = MAP_HEIGHT - 1; + } + entities.entities[i].target.x = x; + entities.entities[i].target.y = y; + } + } updateItems(); updateEntities(); updatePlayer(&player); @@ -723,10 +749,10 @@ void genInitMap() { if (baseType != BGType_WATER_SHALLOW && baseType != BGType_WATER_DEEP) { if (oreNrm > 0.86) { double sub = (oreNrm - 0.86) / (1.0 - 0.86); - if (sub < 0.25) finalType = BGType_PLATINUM_ORE; - else if (sub < 0.50) finalType = BGType_GOLD_ORE; - else if (sub < 0.80) finalType = BGType_SILVER_ORE; - else finalType = BGType_IRON_ORE; + if (sub < 0.25) finalType = BGType_IRON_ORE; + else if (sub < 0.50) finalType = BGType_SILVER_ORE; + else if (sub < 0.80) finalType = BGType_GOLD_ORE; + else finalType = BGType_PLATINUM_ORE; } } diff --git a/player/player.c b/player/player.c index 8c13850..a5c2ee5 100644 --- a/player/player.c +++ b/player/player.c @@ -30,7 +30,9 @@ SDL_Color breakingBarColor = {128, 128, 0, 255}; void setActivePlayerSlot(Player *plr, ItemType activeSlotIndex) { activeSlotIndex = activeSlotIndex % itemRegistryIndex; + if((activeSlotIndex < tileTypeIndex || (activeSlotIndex < itemRegistryIndex && activeSlotIndex >= ITEMREGISTRY_SIZE / 2)) && activeSlotIndex > 0) { plr->inventory.activeSlotIndex = activeSlotIndex; + } } void moveActivePlayerSlot(Player *plr, bool up, bool seek) { @@ -174,7 +176,7 @@ void renderPlayer(Player *plr) { ItemType itemIndex = plr->inventory.activeSlotIndex; //SDL_Texture *itemTex; char itemStringCount[6]; - if (itemIndex < itemRegistryIndex && itemIndex > 0) { + if ((itemIndex < tileTypeIndex || (itemIndex < itemRegistryIndex && itemIndex >= ITEMREGISTRY_SIZE / 2)) && itemIndex > 0) { plr->cursor.heldItemRect.x = plr->cursor.windowX; plr->cursor.heldItemRect.y = plr->cursor.windowY; //itemTex = ItemRegistry[itemIndex].textureOnBelt[plr->cursor.direction]; diff --git a/tiles/tile.c b/tiles/tile.c index fec42b1..bd04607 100644 --- a/tiles/tile.c +++ b/tiles/tile.c @@ -9,6 +9,7 @@ #include "../util/atlas.h" #include "../util/font.h" #include "miner.h" +#include "turret.h" int scrollFrame = 0; unsigned long beltFrames = 0; @@ -80,7 +81,7 @@ void registerTile(char fname[20], SDL_Renderer *renderer) { // Load animation frames int frame = 0; - int indexTile = 0; + ItemType indexTile = 0; char texturePath[80]; if (sscanf(fname, "%d_%20[^_]_%d.png", &indexTile, name, &frame) == 3) { @@ -111,12 +112,13 @@ void registerTile(char fname[20], SDL_Renderer *renderer) { createRotatedTexture(renderer, texture, 270) }; - printf("Bound %s to %d orient %s\n", fname, indexTile, OrientStrings[o]); +// printf("Bound %s to %d orient %s\n", fname, indexTile, OrientStrings[o]); TileRegistry[indexTile].animation.textures[o][frame] = textures[o]; SDL_SetTextureBlendMode(textures[o], SDL_BLENDMODE_BLEND); TileRegistry[indexTile].animation.atlasRects[o][frame] = allocate_32x32(textures[o], renderer); } + printf("Bound %s to %d\n", fname, indexTile); TileRegistry[indexTile].type = indexTile; TileRegistry[indexTile].maxHealth = 200; TileRegistry[indexTile].animation.frameCount = frame + 1; @@ -155,7 +157,7 @@ void registerBackgroundTile(char fname[20], SDL_Renderer *renderer) { SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); - printf("Bound %s to %d\n", fname, indexBgTile); + //printf("Bound %s to %d\n", fname, indexBgTile); BackgroundTileRegistry[indexBgTile].animation.textures[frame] = texture; BackgroundTileRegistry[indexBgTile].animation.atlasRects[frame] = allocate_32x32(texture, renderer); @@ -261,7 +263,7 @@ void setupTiles() { for (uint16_t i = 0; i < ItemSlotCount; i++) { TileRegistry[TYPE_BELT].outputLane[i] = true; } - for (uint16_t l = 0; l < ItemSlotCount; l++) { + for (uint16_t l = 0; l < 2; l++) { for (ItemType i = 0; i < itemRegistryIndex; i++) { TileRegistry[TYPE_BELT].allowedInItems[l][i] = true; } @@ -272,7 +274,7 @@ void setupTiles() { TileRegistry[TYPE_FURNACE].allowedInItems[FURNACE_INPUT_SLOT][i] = true; } } - TileRegistry[TYPE_FURNACE].outputLane[FURNACE_OUTPUT_SLOT] = 1; +; TileRegistry[TYPE_FURNACE].outputLane[FURNACE_OUTPUT_SLOT] = 1; TileRegistry[TYPE_FURNACE].startFrame = 1; TileRegistry[TYPE_FURNACE].needsTicks = true; TileRegistry[TYPE_FURNACE].animation.divisor = 8; @@ -283,6 +285,9 @@ void setupTiles() { TileRegistry[TYPE_MINER].startFrame = 1; TileRegistry[TYPE_AIR].walkable = true; + TileRegistry[TYPE_TURRET].needsTicks = true; + TileRegistry[TYPE_TURRET].allowedInItems[TURRET_AMMO_INPUT_SLOT][IRON_INGOT] = true; + BackgroundTileRegistry[BGType_WATER_DEEP].animation.divisor = 16; BackgroundTileRegistry[BGType_WATER_SHALLOW].animation.divisor = 12; BackgroundTileRegistry[BGType_GRASS_FLOWER0].animation.divisor = 16; @@ -450,6 +455,9 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) { } bool isWalkable(MiniRect tileCoords) { + if (tileCoords.x < 0 || tileCoords.x >= MAP_WIDTH || tileCoords.y < 0 || tileCoords.y >= MAP_HEIGHT) { + return false; + } BackgroundTileType bgt = BackgroundTileRegistry[backgroundMap[tileCoords.y][tileCoords.x].type]; TileTypeReg fgt = TileRegistry[tileMap[tileCoords.y][tileCoords.x].type]; diff --git a/tiles/tile.h b/tiles/tile.h index d5166e8..7d3335a 100644 --- a/tiles/tile.h +++ b/tiles/tile.h @@ -73,10 +73,10 @@ typedef enum BackgroundType { BGType_COBBLE1, BGType_COBBLE2, BGType_COBBLE3, - BGType_PLATINUM_ORE, - BGType_GOLD_ORE, - BGType_SILVER_ORE, BGType_IRON_ORE, + BGType_SILVER_ORE, + BGType_GOLD_ORE, + BGType_PLATINUM_ORE, BGType_END } BackgroundType; diff --git a/tiles/tilecallbacks.c b/tiles/tilecallbacks.c index 64d6193..3be5bcf 100644 --- a/tiles/tilecallbacks.c +++ b/tiles/tilecallbacks.c @@ -5,11 +5,13 @@ #include "tilecallbacks.h" #include "furnace.h" #include "miner.h" +#include "turret.h" const UpdateTileCallback ItemTileCallbacks[TILEREGISTRY_SIZE] = { [TYPE_AIR] = NULL, [TYPE_BLOCK] = NULL, [TYPE_BELT] = NULL, [TYPE_FURNACE] = updateFurnace, - [TYPE_MINER] = updateMiner + [TYPE_MINER] = updateMiner, + [TYPE_TURRET] = updateTurret, }; \ No newline at end of file diff --git a/tiles/turret.c b/tiles/turret.c new file mode 100644 index 0000000..efdc35e --- /dev/null +++ b/tiles/turret.c @@ -0,0 +1,61 @@ +// +// Created by bruno on 6.9.2025. +// + +#include "turret.h" +#include "tile.h" +#include "../util/audio.h" +#include "../entity/entity.h" + +const uint16_t AmmoDamages[ITEMREGISTRY_SIZE] = { + [IRON_INGOT] = 1 +}; + +void updateTurret(Tile *tile) { + ItemOnBelt *inItem = &tile->items[TURRET_AMMO_INPUT_SLOT]; + Item inItemType = ItemRegistry[inItem->type]; + + uint16_t damage = AmmoDamages[inItem->type]; + + if (damage > 0) { + bool foundEnt = false; + + for (int i = 0; i < entities.activeCount; i++) { + Entity *ent = &entities.entities[i]; + int dx = abs(ent->renderRect.x - (tile->rect.x * TILE_SIZE)); + int dy = abs(ent->renderRect.y - (tile->rect.y * TILE_SIZE)); + int d = sqrt(pow(dx, 2) + pow(dy, 2)); + if (d <= (TILE_SIZE * 8)) { + ent->health -= damage; + inItem->type = 0; + tile->audioCh = getAvailableChannel(); + if (tile->audioCh < NUM_SYNTH_VOICES) { + audioData.synthVoices[tile->audioCh].volume = 255; + audioData.synthVoices[tile->audioCh].phase = 0; + audioData.synthVoices[tile->audioCh].sourceRect.x = TILE_SIZE * tile->rect.x; + audioData.synthVoices[tile->audioCh].sourceRect.y = TILE_SIZE * tile->rect.y; + audioData.synthVoices[tile->audioCh].waveform = WAVE_TRIANGLE; + audioData.synthVoices[tile->audioCh].frequency = 400; + } + tile->fixedFrame = 0; + foundEnt = true; + break; + } + } + if (!foundEnt) { + audioData.synthVoices[tile->audioCh].volume = 0; + tile->fixedFrame = 1; + } + + + } else { + if (tile->audioCh < NUM_SYNTH_VOICES) { + audioData.synthVoices[tile->audioCh].volume = 0; + } + tile->fixedFrame = 1; + return; + } + if (audioData.synthVoices[tile->audioCh].frequency > 80) { + audioData.synthVoices[tile->audioCh].frequency--; + } +} \ No newline at end of file diff --git a/tiles/turret.h b/tiles/turret.h new file mode 100644 index 0000000..06222ac --- /dev/null +++ b/tiles/turret.h @@ -0,0 +1,17 @@ +// +// Created by bruno on 6/9/25. +// + +#ifndef FACTORYGAME_TURRET_H +#define FACTORYGAME_TURRET_H + +#include "../items/item.h" +#include "stdint.h" + +extern const uint16_t AmmoDamages[]; + +#define TURRET_AMMO_INPUT_SLOT 0 + +void updateTurret(Tile * tile); + +#endif //FACTORYGAME_TURRET_H