330 lines
9.1 KiB
C
330 lines
9.1 KiB
C
//
|
||
// Created by bruno on 4/24/25.
|
||
//
|
||
|
||
#include <dirent.h>
|
||
#include "util.h"
|
||
#include "../tiles/tile.h"
|
||
#include "font.h"
|
||
//#include "font.h"
|
||
|
||
|
||
ScreenType screenType = SCREEN_MENU;
|
||
|
||
//The window we'll be rendering to
|
||
SDL_Window *window = NULL;
|
||
volatile bool running = true;
|
||
|
||
WaveInfo waveInfo;
|
||
|
||
const char OrientStrings[ORIENT_DIRECTION_COUNT][10] = {
|
||
"LEFT_DOWN",
|
||
"LEFT",
|
||
"LEFT_UP",
|
||
"UP",
|
||
"RIGHT_UP",
|
||
"RIGHT",
|
||
"RIGHT_DOWN",
|
||
"DOWN",
|
||
};
|
||
|
||
int animationStep = 0;
|
||
|
||
bool debugMode = false;
|
||
bool itemViewing = false;
|
||
bool renderAtlas = false;
|
||
|
||
//The surface contained by the window
|
||
SDL_Renderer *mainRenderer = NULL;
|
||
|
||
SDL_Rect screenRect;
|
||
|
||
SDL_Texture *createFlippedTexture(SDL_Renderer *renderer, SDL_Texture *src, SDL_RendererFlip flip) {
|
||
int w, h;
|
||
SDL_QueryTexture(src, NULL, NULL, &w, &h);
|
||
|
||
SDL_Texture *renderTarget = SDL_GetRenderTarget(renderer);
|
||
|
||
SDL_Texture *target = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, w, h);
|
||
SDL_SetRenderTarget(renderer, target);
|
||
SDL_RenderCopyEx(renderer, src, NULL, NULL, 0, NULL, flip);
|
||
SDL_SetRenderTarget(renderer, renderTarget);
|
||
|
||
return target;
|
||
}
|
||
|
||
SDL_Texture *createRotatedTexture(SDL_Renderer *renderer, SDL_Texture *src, double angle) {
|
||
int w, h;
|
||
SDL_QueryTexture(src, NULL, NULL, &w, &h);
|
||
SDL_Texture *renderTarget = SDL_GetRenderTarget(renderer);
|
||
|
||
SDL_Texture *target = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, w, h);
|
||
SDL_SetRenderTarget(renderer, target);
|
||
SDL_RenderCopyEx(renderer, src, NULL, NULL, angle, NULL, SDL_FLIP_NONE);
|
||
SDL_SetRenderTarget(renderer, renderTarget);
|
||
|
||
return target;
|
||
}
|
||
|
||
SDL_Texture *ScaleTexture(SDL_Renderer *renderer, SDL_Texture *src, int newWidth, int newHeight) {
|
||
SDL_Texture *scaledTex = SDL_CreateTexture(renderer,
|
||
SDL_PIXELFORMAT_RGBA8888,
|
||
SDL_TEXTUREACCESS_TARGET,
|
||
newWidth,
|
||
newHeight);
|
||
|
||
if (!scaledTex) {
|
||
SDL_Log("Failed to create target texture: %s", SDL_GetError());
|
||
return nullptr;
|
||
}
|
||
|
||
// Save current render target
|
||
SDL_Texture *oldTarget = SDL_GetRenderTarget(renderer);
|
||
|
||
SDL_SetRenderTarget(renderer, scaledTex);
|
||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||
SDL_RenderClear(renderer);
|
||
|
||
SDL_Rect dst = {0, 0, newWidth, newHeight};
|
||
SDL_RenderCopy(renderer, src, NULL, &dst);
|
||
|
||
SDL_SetRenderTarget(renderer, oldTarget); // Restore
|
||
|
||
return scaledTex;
|
||
}
|
||
|
||
void DrawThickRect(SDL_Renderer *renderer, SDL_Rect rect, int thickness) {
|
||
for (int i = 0; i < thickness; i++) {
|
||
SDL_Rect r = {rect.x - i, rect.y - i, rect.w + i * 2, rect.h + i * 2};
|
||
SDL_RenderDrawRect(renderer, &r);
|
||
}
|
||
}
|
||
|
||
void renderBar(SDL_Renderer *renderer,
|
||
int x, int y, int width, int height,
|
||
int maxValue, int currentValue,
|
||
SDL_Color barColor, int margin) {
|
||
if (maxValue <= 0) return; // Avoid division by zero
|
||
|
||
// Clamp value
|
||
if (currentValue < 0) currentValue = 0;
|
||
if (currentValue == 0) {
|
||
return;
|
||
}
|
||
if (currentValue > maxValue) currentValue = maxValue;
|
||
|
||
// Calculate filled width based on currentValue
|
||
int filledWidth = (width * currentValue) / maxValue;
|
||
|
||
// Bar rectangle
|
||
SDL_Rect barRect = {x, y, filledWidth, height};
|
||
|
||
// Background rectangle with margin
|
||
SDL_Rect bgRect = {
|
||
x - margin,
|
||
y - margin,
|
||
width + margin * 2,
|
||
height + margin * 2
|
||
};
|
||
|
||
// Draw background (black)
|
||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||
SDL_RenderFillRect(renderer, &bgRect);
|
||
|
||
// Draw bar with provided color
|
||
SDL_SetRenderDrawColor(renderer, barColor.r, barColor.g, barColor.b, barColor.a);
|
||
SDL_RenderFillRect(renderer, &barRect);
|
||
|
||
char barString[20];
|
||
sprintf(barString, "%d/%d", currentValue, maxValue);
|
||
|
||
renderText(mainRenderer, fonts[1], barString, x + (width / 2 - (fonts[2].size * strlen(barString))),
|
||
y - fonts[2].size - barRect.h);
|
||
}
|
||
|
||
int cmpstringp(const void *p1, const void *p2) {
|
||
return strcmp(*(const char **) p1, *(const char **) p2);
|
||
}
|
||
|
||
|
||
// Helper function to iterate over sorted entries in a directory
|
||
void iterateSortedDir(const char *path, DirEntryCallback callback, SDL_Renderer *renderer) {
|
||
DIR *dir = opendir(path);
|
||
if (!dir) {
|
||
perror("opendir");
|
||
return;
|
||
}
|
||
|
||
struct dirent *entry;
|
||
char **names = NULL;
|
||
size_t count = 0;
|
||
|
||
// Collect file names
|
||
while ((entry = readdir(dir)) != NULL) {
|
||
if (entry->d_name[0] == '.') continue;
|
||
names = realloc(names, sizeof(char *) * (count + 1));
|
||
if (!names) {
|
||
perror("realloc");
|
||
closedir(dir);
|
||
return;
|
||
}
|
||
names[count++] = strdup(entry->d_name);
|
||
}
|
||
closedir(dir);
|
||
|
||
// Sort entries
|
||
qsort(names, count, sizeof(char *), cmpstringp);
|
||
|
||
// Call the user-provided function for each file
|
||
if (names != NULL) {
|
||
for (size_t i = 0; i < count; i++) {
|
||
if (names[i] != NULL) {
|
||
callback(names[i], renderer);
|
||
free(names[i]);
|
||
}
|
||
}
|
||
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 mainPlayer
|
||
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);
|
||
}
|
||
|
||
|
||
double angle_between_points_deg(double x1, double y1, double x2, double y2) {
|
||
double dx = x2 - x1;
|
||
double dy = y2 - y1;
|
||
double angle_rad = atan2(dy, dx);
|
||
double angle_deg = angle_rad * (180.0 / M_PI);
|
||
|
||
// Normalize to 0–360 degrees (optional, depending on use case)
|
||
if (angle_deg < 0) {
|
||
angle_deg += 360.0;
|
||
}
|
||
|
||
return angle_deg;
|
||
}
|
||
|
||
#define TICKS_PER_SECOND 60
|
||
|
||
void initWaveInfo(WaveInfo *info) {
|
||
info->waveCounter = 0;
|
||
info->waveTimer = 200 * TICKS_PER_SECOND; // 200 seconds before first wave
|
||
info->waveRunning = false;
|
||
info->totalWaves = MAX_WAVES;
|
||
|
||
for (int i = 0; i < MAX_WAVES; i++) {
|
||
int flyerCount = 1 + i * 2; // Each wave increases flyer count
|
||
|
||
info->waves[i].enemyCount = 1;
|
||
info->waves[i].enemies[0] = (EnemyEntry) {ENEMY_TYPE_FLYER, flyerCount};
|
||
|
||
// Reduce time between waves gradually (but not below 5 seconds)
|
||
int secondsBetween = 15 - (i / 5);
|
||
if (secondsBetween < 5) secondsBetween = 5;
|
||
info->waves[i].timeUntilNext = secondsBetween * TICKS_PER_SECOND;
|
||
}
|
||
}
|