Hopefully last commit
This commit is contained in:
212
util/button.c
Normal file
212
util/button.c
Normal file
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// Created by bruno on 11.6.2025.
|
||||
//
|
||||
|
||||
#include "button.h"
|
||||
#include "gamestate.h"
|
||||
|
||||
void onStart() {
|
||||
genInitMap();
|
||||
screenType = SCREEN_GAME;
|
||||
}
|
||||
|
||||
void onContinue() {
|
||||
if (loadGameState(autosaveName, &mainPlayer)) {
|
||||
genInitMap();
|
||||
}
|
||||
screenType = SCREEN_GAME;
|
||||
}
|
||||
|
||||
void onCredits() {
|
||||
screenType = SCREEN_CREDITS;
|
||||
}
|
||||
|
||||
void onNext() {
|
||||
if (!waveInfo.waveRunning && waveInfo.waveTimer > 60 * 5) {
|
||||
waveInfo.waveTimer = 60 * 5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void onQuit() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
void onBack() {
|
||||
screenType = SCREEN_MENU;
|
||||
}
|
||||
|
||||
void onAtlas() {
|
||||
screenType = SCREEN_ATLAS;
|
||||
}
|
||||
|
||||
void onFont() {
|
||||
screenType = SCREEN_FONTS;
|
||||
}
|
||||
|
||||
Button buttons[5][BUTTON_COUNT];
|
||||
|
||||
void initButtons(void) {
|
||||
buttons[0][0] = (Button){
|
||||
.label = "New",
|
||||
.rect = {110, DISPLAY_HEIGHT / 2, 125, 40},
|
||||
.color = {100, 100, 0, 255},
|
||||
.callback = onStart,
|
||||
.font = &fonts[1]
|
||||
};
|
||||
|
||||
buttons[0][1] = (Button){
|
||||
.label = "Continue",
|
||||
.rect = {100, DISPLAY_HEIGHT / 2 + 45, 150, 40},
|
||||
.color = {0, 100, 0, 255},
|
||||
.callback = onContinue,
|
||||
.font = &fonts[1]
|
||||
};
|
||||
|
||||
buttons[0][2] = (Button){
|
||||
.label = "Credits",
|
||||
.rect = {DISPLAY_WIDTH - 200, DISPLAY_HEIGHT - 120, 150, 40},
|
||||
.color = {0, 50, 50, 255},
|
||||
.callback = onCredits,
|
||||
.font = &fonts[1]
|
||||
};
|
||||
|
||||
buttons[0][3] = (Button){
|
||||
.label = "Exit",
|
||||
.rect = {DISPLAY_WIDTH - 200, DISPLAY_HEIGHT - 40, 100, 40},
|
||||
.color = {100, 0, 0, 255},
|
||||
.callback = onQuit,
|
||||
.font = &fonts[1]
|
||||
};
|
||||
|
||||
buttons[1][0] = (Button){
|
||||
.label = "Back",
|
||||
.rect = {DISPLAY_WIDTH / 2 - (100 / 2), DISPLAY_HEIGHT - 40, 100, 40},
|
||||
.color = {100, 0, 0, 255},
|
||||
.callback = onBack,
|
||||
.font = &fonts[1]
|
||||
};
|
||||
|
||||
buttons[1][1] = (Button){
|
||||
.label = "Font atlas",
|
||||
.rect = {DISPLAY_WIDTH - 900, DISPLAY_HEIGHT - 40, 200, 40},
|
||||
.color = {100, 0, 0, 255},
|
||||
.callback = onFont,
|
||||
.font = &fonts[1]
|
||||
};
|
||||
|
||||
buttons[1][2] = (Button){
|
||||
.label = "Texture atlas",
|
||||
.rect = {DISPLAY_WIDTH - 300, DISPLAY_HEIGHT - 40, 250, 40},
|
||||
.color = {100, 0, 0, 255},
|
||||
.callback = onAtlas,
|
||||
.font = &fonts[1]
|
||||
};
|
||||
|
||||
buttons[2][0] = (Button){
|
||||
.label = "Next",
|
||||
.rect = {395, 0, 55, 80},
|
||||
.color = {64, 64, 64, 64},
|
||||
.callback = onNext,
|
||||
.font = &fonts[2]
|
||||
};
|
||||
|
||||
buttons[3][0] = (Button){
|
||||
.label = "Back",
|
||||
.rect = {DISPLAY_WIDTH / 2 - (400 / 2), DISPLAY_HEIGHT - 40, 100, 40},
|
||||
.color = {100, 0, 0, 255},
|
||||
.callback = onCredits,
|
||||
.font = &fonts[1]
|
||||
};
|
||||
|
||||
buttons[4][0] = (Button){
|
||||
.label = "Back",
|
||||
.rect = {DISPLAY_WIDTH / 2 - (400 / 2), DISPLAY_HEIGHT - 40, 100, 40},
|
||||
.color = {100, 0, 0, 255},
|
||||
.callback = onCredits,
|
||||
.font = &fonts[1]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
void renderButtons(SDL_Renderer *renderer, Player player) {
|
||||
for (int i = 0; i < BUTTON_COUNT; i++) {
|
||||
renderButton(renderer, buttons[screenType][i], player);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void renderButton(SDL_Renderer *renderer, Button btn, Player player) {
|
||||
if (btn.font == NULL || btn.label == NULL || strlen(btn.label) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for hover/click
|
||||
bool hovered = (player.cursor.windowX >= btn.rect.x && player.cursor.windowX <= btn.rect.x + btn.rect.w &&
|
||||
player.cursor.windowY >= btn.rect.y && player.cursor.windowY <= btn.rect.y + btn.rect.h);
|
||||
|
||||
// Set color and draw background
|
||||
SDL_Color bgColor = btn.color;
|
||||
if (hovered) {
|
||||
bgColor.a = btn.color.a / 2;
|
||||
}
|
||||
SDL_SetRenderDrawColor(renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a);
|
||||
SDL_RenderFillRect(renderer, &btn.rect);
|
||||
|
||||
// Split label into lines
|
||||
const char *label = btn.label;
|
||||
const int maxLines = 10; // Hard cap for sanity
|
||||
const char *lines[maxLines];
|
||||
int lineCount = 0;
|
||||
|
||||
lines[lineCount++] = label;
|
||||
for (const char *p = label; *p && lineCount < maxLines; p++) {
|
||||
if (*p == '\n') {
|
||||
lines[lineCount++] = p + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Get length of each line and find max width (for centering)
|
||||
int maxLineWidth = 0;
|
||||
int lineWidths[maxLines];
|
||||
int fontSize = btn.font->size;
|
||||
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
const char *lineStart = lines[i];
|
||||
const char *lineEnd = strchr(lineStart, '\n');
|
||||
int lineLen = lineEnd ? (lineEnd - lineStart) : strlen(lineStart);
|
||||
|
||||
int lineWidth = lineLen * fontSize;
|
||||
lineWidths[i] = lineWidth;
|
||||
if (lineWidth > maxLineWidth) {
|
||||
maxLineWidth = lineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate starting Y to vertically center all lines
|
||||
int totalHeight = lineCount * fontSize;
|
||||
int startY = btn.rect.y + (btn.rect.h - totalHeight) / 2;
|
||||
|
||||
// Render each line centered horizontally
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
const char *lineStart = lines[i];
|
||||
const char *lineEnd = strchr(lineStart, '\n');
|
||||
int lineLen = lineEnd ? (lineEnd - lineStart) : strlen(lineStart);
|
||||
|
||||
char buffer[256];
|
||||
strncpy(buffer, lineStart, lineLen);
|
||||
buffer[lineLen] = '\0';
|
||||
|
||||
int x = btn.rect.x + (btn.rect.w - lineWidths[i]) / 2;
|
||||
int y = startY + i * fontSize;
|
||||
|
||||
renderText(renderer, *btn.font, buffer, x, y);
|
||||
}
|
||||
|
||||
// Trigger callback if clicked
|
||||
if (hovered && (player.mouseButtons & SDL_BUTTON_LMASK)) {
|
||||
if (btn.callback) {
|
||||
btn.callback();
|
||||
}
|
||||
}
|
||||
}
|
28
util/button.h
Normal file
28
util/button.h
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by bruno on 11.6.2025.
|
||||
//
|
||||
|
||||
#ifndef FACTORYGAME_BUTTON_H
|
||||
#define FACTORYGAME_BUTTON_H
|
||||
#include "SDL2/SDL.h"
|
||||
#include "font.h"
|
||||
#include "../player/player.h"
|
||||
|
||||
typedef struct {
|
||||
char* label;
|
||||
SDL_Rect rect;
|
||||
SDL_Color color;
|
||||
void (*callback)(void);
|
||||
BitmapFont *font;
|
||||
} Button;
|
||||
|
||||
#define BUTTON_COUNT 10
|
||||
|
||||
extern Button buttons[5][BUTTON_COUNT];
|
||||
void initButtons(void);
|
||||
|
||||
void renderButton(SDL_Renderer* renderer, Button btn, Player player);
|
||||
|
||||
void renderButtons(SDL_Renderer *renderer, Player player);
|
||||
|
||||
#endif //FACTORYGAME_BUTTON_H
|
@@ -17,7 +17,7 @@ enum {
|
||||
MACHINE_SLOTS = 3
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
typedef struct MachineRecipe {
|
||||
ItemType input1;
|
||||
ItemType input2; // use TYPE_AIR if single-input
|
||||
ItemType output;
|
||||
@@ -34,4 +34,4 @@ bool findMachineRecipe(const MachineRecipe *recipes, size_t count, ItemType in1,
|
||||
void initMachineTile(ItemType type, const MachineRecipe *recipes, size_t count,
|
||||
uint8_t startFrame, uint8_t divisor);
|
||||
|
||||
#endif //FACTORYGAME_CRAFTER_H
|
||||
#endif //FACTORYGAME_AMMOCRAFTER_H
|
||||
|
61
util/gamestate.c
Normal file
61
util/gamestate.c
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// Created by bruno on 11.6.2025.
|
||||
//
|
||||
|
||||
#include "gamestate.h"
|
||||
|
||||
GameState gameState;
|
||||
|
||||
char *autosaveName = "autosave.dat";
|
||||
|
||||
int loadGameState(char *filename, Player *plr) {
|
||||
fflush(stdout);
|
||||
FILE *gameSave = fopen(filename, "rb");
|
||||
if (gameSave) {
|
||||
fseek(gameSave, 0L, SEEK_END);
|
||||
long sz = ftell(gameSave);
|
||||
if (sz != sizeof(gameState)) {
|
||||
return 1;
|
||||
}
|
||||
rewind(gameSave);
|
||||
fread(&gameState, sizeof(gameState), 1, gameSave);
|
||||
fclose(gameSave);
|
||||
memcpy(plr, &gameState.player, sizeof(gameState.player));
|
||||
memcpy(tileMap, gameState.tileMap, sizeof(tileMap));
|
||||
memcpy(backgroundMap, gameState.backgroundTileMap, sizeof(backgroundMap));
|
||||
SDL_Rect *tmp = audioData.playerRect;
|
||||
memcpy(&audioData, &gameState.audioData, sizeof(gameState.audioData));
|
||||
audioData.playerRect = tmp;
|
||||
audioData.totalSamples = 0;
|
||||
memcpy(&neededUpdates, &gameState.neededUpdates, sizeof(gameState.neededUpdates));
|
||||
memcpy(&entities, &gameState.entities, sizeof(gameState.entities));
|
||||
openCount = gameState.openCount;
|
||||
memcpy(&openList, &gameState.openList, sizeof(gameState.openList));
|
||||
memcpy(&enemySpawn, &gameState.enemySpawn, sizeof(gameState.enemySpawn));
|
||||
plr->cursor.targetTile = NULL;
|
||||
plr->cursor.prevTargetTile = NULL;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void saveGameState(char *filename, Player *plr) {
|
||||
memcpy(&gameState.player, plr, sizeof(gameState.player));
|
||||
memcpy(gameState.tileMap, tileMap, sizeof(gameState.tileMap));
|
||||
memcpy(gameState.backgroundTileMap, backgroundMap, sizeof(gameState.backgroundTileMap));
|
||||
memcpy(&gameState.audioData, &audioData, sizeof(gameState.audioData));
|
||||
memcpy(&gameState.neededUpdates, &neededUpdates, sizeof(neededUpdates));
|
||||
memcpy(&gameState.entities, &entities, sizeof(entities));
|
||||
memcpy(&gameState.openList, &openList, sizeof(openList));
|
||||
memcpy(&gameState.enemySpawn, &enemySpawn, sizeof(enemySpawn));
|
||||
gameState.openCount = openCount;
|
||||
|
||||
FILE *gameSave = fopen(filename, "wb");
|
||||
if (!gameSave) {
|
||||
perror("Failed to open file for saving");
|
||||
return;
|
||||
}
|
||||
|
||||
fwrite(&gameState, sizeof(gameState), 1, gameSave);
|
||||
fclose(gameSave);
|
||||
}
|
33
util/gamestate.h
Normal file
33
util/gamestate.h
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Created by bruno on 11.6.2025.
|
||||
//
|
||||
|
||||
#ifndef FACTORYGAME_GAMESTATE_H
|
||||
#define FACTORYGAME_GAMESTATE_H
|
||||
|
||||
|
||||
#include "../tiles/tile.h"
|
||||
#include "../player/player.h"
|
||||
#include "audio.h"
|
||||
#include "../entity/entity.h"
|
||||
|
||||
typedef struct GameState {
|
||||
Player player;
|
||||
Tile tileMap[MAP_HEIGHT][MAP_WIDTH];
|
||||
BackgroundTile backgroundTileMap[MAP_HEIGHT][MAP_WIDTH];
|
||||
AudioData audioData;
|
||||
TileArray neededUpdates;
|
||||
EntityArray entities;
|
||||
Node openList[MAX_OPEN_NODES];
|
||||
int openCount;
|
||||
MiniRect enemySpawn;
|
||||
} GameState;
|
||||
|
||||
int loadGameState(char *filename, Player *plr);
|
||||
|
||||
void saveGameState(char *filename, Player *plr);
|
||||
|
||||
extern GameState gameState;
|
||||
extern char *autosaveName;
|
||||
|
||||
#endif //FACTORYGAME_GAMESTATE_H
|
53
util/util.c
53
util/util.c
@@ -9,6 +9,8 @@
|
||||
//#include "font.h"
|
||||
|
||||
|
||||
ScreenType screenType = SCREEN_MENU;
|
||||
|
||||
//The window we'll be rendering to
|
||||
SDL_Window *window = NULL;
|
||||
volatile bool running = true;
|
||||
@@ -136,7 +138,8 @@ void renderBar(SDL_Renderer *renderer,
|
||||
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);
|
||||
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) {
|
||||
@@ -226,14 +229,14 @@ bool checkCollision(SDL_Rect a, SDL_Rect b) {
|
||||
|
||||
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 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 };
|
||||
MiniRect tile = {tx, ty};
|
||||
if (!isWalkable(tile)) {
|
||||
return false;
|
||||
}
|
||||
@@ -265,7 +268,7 @@ bool canMoveWithRadius(SDL_Rect centerRect) {
|
||||
int tileTop = y * TILE_SIZE;
|
||||
int tileBottom = tileTop + TILE_SIZE;
|
||||
|
||||
// Bounding box of the player
|
||||
// Bounding box of the mainPlayer
|
||||
int playerLeft = centerRect.x - radius;
|
||||
int playerRight = centerRect.x + radius;
|
||||
int playerTop = centerRect.y - radius;
|
||||
@@ -287,4 +290,40 @@ 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;
|
||||
}
|
||||
}
|
||||
|
43
util/util.h
43
util/util.h
@@ -49,13 +49,50 @@ typedef struct MiniRect {
|
||||
int y;
|
||||
} MiniRect;
|
||||
|
||||
#define MAX_ENEMIES_PER_WAVE 16
|
||||
#define MAX_WAVES 128
|
||||
|
||||
// Define enemy types (expand as needed)
|
||||
typedef enum EnemyType {
|
||||
ENEMY_TYPE_NONE = 0,
|
||||
ENEMY_TYPE_FLYER,
|
||||
// Add more enemy types here
|
||||
} EnemyType;
|
||||
|
||||
typedef struct EnemyEntry {
|
||||
EnemyType type;
|
||||
int count;
|
||||
} EnemyEntry;
|
||||
|
||||
|
||||
typedef struct Wave {
|
||||
EnemyEntry enemies[MAX_ENEMIES_PER_WAVE];
|
||||
int enemyCount; // Number of different enemy types in this wave
|
||||
int timeUntilNext; // Time until the next wave (in ticks, ms, etc.)
|
||||
} Wave;
|
||||
|
||||
typedef struct WaveInfo {
|
||||
int waveCounter;
|
||||
int waveTimer;
|
||||
int waveCounter; // Current wave number (index)
|
||||
int waveTimer; // Countdown until next wave starts
|
||||
bool waveRunning; // Whether a wave is currently running
|
||||
Wave waves[MAX_WAVES]; // List of all waves
|
||||
int totalWaves; // Total number of defined waves
|
||||
} WaveInfo;
|
||||
|
||||
typedef enum ScreenType {
|
||||
SCREEN_MENU,
|
||||
SCREEN_CREDITS,
|
||||
SCREEN_GAME,
|
||||
SCREEN_FONTS,
|
||||
SCREEN_ATLAS,
|
||||
} ScreenType;
|
||||
|
||||
extern ScreenType screenType;
|
||||
|
||||
extern WaveInfo waveInfo;
|
||||
|
||||
void initWaveInfo(WaveInfo *info);
|
||||
|
||||
|
||||
typedef struct OrientedAnimation {
|
||||
SDL_Rect atlasRects[ORIENT_DIRECTION_COUNT][TILE_SIZE * 2];
|
||||
@@ -92,4 +129,6 @@ bool canMoveTo(SDL_Rect newRect);
|
||||
|
||||
bool canMoveWithRadius(SDL_Rect centerRect);
|
||||
|
||||
double angle_between_points_deg(double x1, double y1, double x2, double y2);
|
||||
|
||||
#endif //FACTORYGAME_UTIL_H
|
Reference in New Issue
Block a user