start turret

This commit is contained in:
2025-06-10 16:29:17 +02:00
parent 79c8b747cd
commit a17e3abbff
17 changed files with 213 additions and 68 deletions

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

View File

@@ -7,6 +7,7 @@
#include "entity.h"
#include "../player/player.h"
#include "../util/pathfinding.h"
#include "../util/font.h"
EntityArray entities;
@@ -26,23 +27,36 @@ 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) {
// 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;
@@ -51,15 +65,14 @@ void updateEntities() {
ent->toTile = ent->path.steps[0];
ent->interpolateTick = 0;
ent->entityNextTick = animationStep + entT.entityTickRate;
} else {
// No path found — freeze
} 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;

View File

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

View File

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

View File

@@ -19,6 +19,7 @@ typedef enum ItemType {
TYPE_BELT,
TYPE_FURNACE,
TYPE_MINER,
TYPE_TURRET,
IRON_ORE = ITEMREGISTRY_SIZE / 2,
SILVER_ORE,
GOLD_ORE,

64
main.c
View File

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

View File

@@ -30,8 +30,10 @@ 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) {
ItemType prevSlot = plr->inventory.activeSlotIndex;
@@ -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];

View File

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

View File

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

View File

@@ -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,
};

61
tiles/turret.c Normal file
View File

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

17
tiles/turret.h Normal file
View File

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