Hopefully last commit
This commit is contained in:
161
entity/entity.c
161
entity/entity.c
@@ -14,6 +14,8 @@ EntityArray entities;
|
||||
|
||||
EntityTypeReg EntityRegistry[ENTITY_MAX_COUNT];
|
||||
|
||||
EnemySpawnState currentSpawnState;
|
||||
|
||||
void renderEntities(SDL_Renderer *renderer, SDL_Rect playerRect) {
|
||||
SDL_Texture *oldTarget = SDL_GetRenderTarget(renderer);
|
||||
SDL_SetRenderTarget(renderer, entityTexture);
|
||||
@@ -44,6 +46,7 @@ void updateEntities(Player *plr) {
|
||||
remove_entity(&entities, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool atTargetSnapshot = ent->tileRect.x == ent->targetSnapshot.x &&
|
||||
ent->tileRect.y == ent->targetSnapshot.y;
|
||||
|
||||
@@ -51,22 +54,19 @@ void updateEntities(Player *plr) {
|
||||
ent->tileRect.y == ent->target.y;
|
||||
|
||||
if (animationStep >= ent->entityNextTick) {
|
||||
if (sqrt(pow(abs(plr->tileRect.x - ent->tileRect.x), 2) + pow(abs(plr->tileRect.y - ent->tileRect.y), 2)) < ENEMY_RANGE) {
|
||||
if (sqrt(pow(abs(plr->tileRect.x - ent->tileRect.x), 2) +
|
||||
pow(abs(plr->tileRect.y - ent->tileRect.y), 2)) < ENEMY_RANGE) {
|
||||
plr->health -= ENEMY_DAMAGE;
|
||||
}
|
||||
if (plr->tileRect.x)
|
||||
for (int y = ent->tileRect.y - ENEMY_RANGE; y < ent->tileRect.y + ENEMY_RANGE; y++) {
|
||||
if (y < 0 || y >= MAP_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
for (int x = ent->tileRect.x - ENEMY_RANGE; x < ent->tileRect.x + ENEMY_RANGE; x++) {
|
||||
if (x < 0 || x >= MAP_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int y = ent->tileRect.y - ENEMY_RANGE; y <= ent->tileRect.y + ENEMY_RANGE; y++) {
|
||||
if (y < 0 || y >= MAP_HEIGHT) continue;
|
||||
for (int x = ent->tileRect.x - ENEMY_RANGE; x <= ent->tileRect.x + ENEMY_RANGE; x++) {
|
||||
if (x < 0 || x >= MAP_WIDTH) continue;
|
||||
|
||||
Tile *targTile = &tileMap[y][x];
|
||||
if (targTile->type == TYPE_AIR) {
|
||||
continue;
|
||||
}
|
||||
if (targTile->type == TYPE_AIR) continue;
|
||||
|
||||
targTile->health -= ENEMY_DAMAGE;
|
||||
if (targTile->health <= 0) {
|
||||
if (targTile->audioCh < NUM_SYNTH_VOICES) {
|
||||
@@ -79,31 +79,63 @@ void updateEntities(Player *plr) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
MiniRect fallbackTarget = ent->target;
|
||||
|
||||
// If the target is not walkable, search nearby
|
||||
if (!isWalkable(ent->target)) {
|
||||
int bestDist = 999999;
|
||||
bool found = false;
|
||||
|
||||
for (int dy = -5; dy <= 5; dy++) {
|
||||
for (int dx = -5; dx <= 5; dx++) {
|
||||
int nx = ent->target.x + dx;
|
||||
int ny = ent->target.y + dy;
|
||||
|
||||
if (nx < 0 || ny < 0 || nx >= MAP_WIDTH || ny >= MAP_HEIGHT) continue;
|
||||
|
||||
MiniRect check = {nx, ny};
|
||||
|
||||
if (!isWalkable(check)) continue;
|
||||
|
||||
int dist = abs(dx) + abs(dy); // Manhattan distance
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
fallbackTarget = check;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// No walkable fallback tile found
|
||||
ent->path.length = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt pathfinding to fallbackTarget
|
||||
if (find_path(ent->tileRect, fallbackTarget)) {
|
||||
ent->path = reconstruct_path(fallbackTarget);
|
||||
ent->path.stepIndex = 0;
|
||||
ent->targetSnapshot = ent->target; // snapshot the current target
|
||||
ent->targetSnapshot = fallbackTarget;
|
||||
ent->fromTile = ent->tileRect;
|
||||
ent->toTile = ent->path.steps[0];
|
||||
ent->interpolateTick = 0;
|
||||
ent->entityNextTick = animationStep + entT.entityTickRate;
|
||||
} else if (atTargetSnapshot) {
|
||||
// No path found and we finished current one — freeze
|
||||
ent->path.length = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Movement
|
||||
// Movement
|
||||
if (ent->path.length > 0 && ent->path.stepIndex < ent->path.length &&
|
||||
animationStep >= ent->entityNextTick) {
|
||||
ent->fromTile = ent->tileRect;
|
||||
@@ -114,7 +146,7 @@ void updateEntities(Player *plr) {
|
||||
ent->path.stepIndex++;
|
||||
}
|
||||
|
||||
// Step 3: Interpolation
|
||||
// Interpolation
|
||||
MiniRect from = {
|
||||
.x = ent->fromTile.x * TILE_SIZE,
|
||||
.y = ent->fromTile.y * TILE_SIZE
|
||||
@@ -166,7 +198,7 @@ void registerEntity(char fname[20], SDL_Renderer *renderer) {
|
||||
EntityRegistry[indexEntity].type = indexEntity;
|
||||
EntityRegistry[indexEntity].animation.frameCount = frame + 1;
|
||||
EntityRegistry[indexEntity].animation.divisor = 1;
|
||||
EntityRegistry[indexEntity].entityTickRate = 8;
|
||||
EntityRegistry[indexEntity].entityTickRate = 16;
|
||||
EntityRegistry[indexEntity].maxHealth = 100;
|
||||
|
||||
if (indexEntity + 1 > backgroundTileTypeIndex) {
|
||||
@@ -213,6 +245,7 @@ void loadEntities(SDL_Renderer *renderer) {
|
||||
registerEntity(fileName, renderer);
|
||||
free(entityNames[i]);
|
||||
}
|
||||
EntityRegistry[GHOST].animation.divisor = 16;
|
||||
}
|
||||
|
||||
int add_entity(EntityArray *arr, Entity t) {
|
||||
@@ -226,4 +259,88 @@ 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
|
||||
}
|
||||
|
||||
|
||||
bool isTileAlreadyTargeted(int x, int y) {
|
||||
for (int i = 0; i < entities.activeCount; i++) {
|
||||
Entity *ent = &entities.entities[i];
|
||||
if (ent->target.x == x && ent->target.y == y) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spawn_enemy_at_random_tile() {
|
||||
Entity ent;
|
||||
memset(&ent, 0, sizeof(Entity));
|
||||
|
||||
// Start by placing it at a default location (same as before)
|
||||
int offsetX = (rand() % (SPAWN_RADIUS * 2 + 1)) - SPAWN_RADIUS;
|
||||
int offsetY = (rand() % (SPAWN_RADIUS * 2 + 1)) - SPAWN_RADIUS;
|
||||
|
||||
ent.tileRect.x = enemySpawn.x + offsetX;
|
||||
ent.tileRect.y = enemySpawn.y + offsetY;
|
||||
ent.renderRect.x = ent.tileRect.x * TILE_SIZE;
|
||||
ent.renderRect.y = ent.tileRect.y * TILE_SIZE;
|
||||
ent.renderRect.w = TILE_SIZE;
|
||||
ent.renderRect.h = TILE_SIZE;
|
||||
ent.health = 100;
|
||||
ent.type = GHOST;
|
||||
|
||||
// Try to find a unique, walkable target tile near the player
|
||||
for (int attempt = 0; attempt < MAX_SPAWN_ATTEMPTS; attempt++) {
|
||||
int targetOffsetX = (rand() % (SPAWN_RADIUS * 2 + 1)) - SPAWN_RADIUS;
|
||||
int targetOffsetY = (rand() % (SPAWN_RADIUS * 2 + 1)) - SPAWN_RADIUS;
|
||||
|
||||
int targetX = mainPlayer.tileRect.x + targetOffsetX;
|
||||
int targetY = mainPlayer.tileRect.y + targetOffsetY;
|
||||
MiniRect target = {targetX, targetY};
|
||||
|
||||
if (!isWalkable(target)) continue;
|
||||
if (isTileAlreadyTargeted(targetX, targetY)) continue;
|
||||
|
||||
ent.target.x = targetX;
|
||||
ent.target.y = targetY;
|
||||
|
||||
add_entity(&entities, ent);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Failed to spawn enemy: no unique target found after %d attempts.\n", MAX_SPAWN_ATTEMPTS);
|
||||
}
|
||||
void updateWaveLogic(WaveInfo *info) {
|
||||
if (info->waveRunning) {
|
||||
Wave *currentWave = &info->waves[info->waveCounter];
|
||||
|
||||
// Cooldown between enemy spawns
|
||||
if (--currentSpawnState.spawnCooldown <= 0 &&
|
||||
currentSpawnState.enemiesSpawned < currentWave->enemies[0].count) {
|
||||
|
||||
spawn_enemy_at_random_tile();
|
||||
|
||||
currentSpawnState.enemiesSpawned++;
|
||||
currentSpawnState.spawnCooldown = SPAWN_COOLDOWN;
|
||||
}
|
||||
|
||||
// All enemies spawned
|
||||
if (currentSpawnState.enemiesSpawned >= currentWave->enemies[0].count) {
|
||||
// Wait for all enemies to be dead before ending wave
|
||||
if (entities.activeCount == 0) {
|
||||
info->waveRunning = false;
|
||||
info->waveTimer = info->waves[info->waveCounter].timeUntilNext;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Timer starts only after wave is completely over (including enemies)
|
||||
if (--info->waveTimer <= 0 && info->waveCounter + 1 < info->totalWaves) {
|
||||
// Start next wave
|
||||
info->waveCounter++;
|
||||
info->waveRunning = true;
|
||||
|
||||
currentSpawnState.enemiesSpawned = 0;
|
||||
currentSpawnState.spawnCooldown = 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,9 +11,22 @@
|
||||
|
||||
#define ENTITY_MAX_COUNT 1024
|
||||
|
||||
#define ENEMY_DAMAGE 2
|
||||
#define ENEMY_DAMAGE 1
|
||||
#define ENEMY_RANGE 3
|
||||
|
||||
#define SPAWN_RADIUS 5
|
||||
|
||||
typedef struct EnemySpawnState {
|
||||
int enemiesSpawned;
|
||||
int spawnCooldown;
|
||||
} EnemySpawnState;
|
||||
|
||||
#define SPAWN_COOLDOWN 30 // frames between each enemy spawn
|
||||
|
||||
#define MAX_SPAWN_ATTEMPTS 50
|
||||
|
||||
extern EnemySpawnState currentSpawnState;
|
||||
|
||||
typedef enum EntityType {
|
||||
GHOST,
|
||||
} EntityType;
|
||||
@@ -56,5 +69,6 @@ void renderEntities(SDL_Renderer *renderer, SDL_Rect playerRect);
|
||||
void updateEntities(Player * plr);
|
||||
void registerEntity(char fname[20], SDL_Renderer *renderer);
|
||||
void loadEntities(SDL_Renderer *renderer);
|
||||
void updateWaveLogic(WaveInfo* info);
|
||||
|
||||
#endif //FACTORYGAME_ENTITY_H
|
||||
|
Reference in New Issue
Block a user