summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clangd3
-rw-r--r--Makefile23
-rw-r--r--all.h117
-rw-r--r--config.h8
-rw-r--r--game.c306
-rw-r--r--libraries/nonstd.h87
-rw-r--r--libraries/vfs.h16
-rw-r--r--main.c28
-rw-r--r--map.c246
-rw-r--r--maps/demo1.map14
-rw-r--r--textures/brushes/bricks_076c.jpgbin96128 -> 0 bytes
-rw-r--r--textures/brushes/bricks_076c.pngbin0 -> 111850 bytes
-rw-r--r--textures/brushes/ground_068.jpgbin129649 -> 0 bytes
-rw-r--r--textures/brushes/ground_068.pngbin0 -> 129984 bytes
-rw-r--r--textures/brushes/rock_016.jpgbin57831 -> 0 bytes
-rw-r--r--textures/brushes/rock_016.pngbin0 -> 97693 bytes
-rw-r--r--textures/brushes/rock_030.jpgbin88953 -> 0 bytes
-rw-r--r--textures/brushes/rock_030.pngbin0 -> 123792 bytes
-rw-r--r--textures/environment/paintedwood_008a.jpgbin362255 -> 0 bytes
-rw-r--r--textures/environment/paintedwood_008a.pngbin0 -> 477538 bytes
-rw-r--r--textures/environment/planks_012.jpgbin346294 -> 0 bytes
-rw-r--r--textures/environment/planks_012.pngbin0 -> 422134 bytes
-rw-r--r--textures/environment/roofingtiles_012b.jpgbin263430 -> 0 bytes
-rw-r--r--textures/environment/roofingtiles_012b.pngbin0 -> 370240 bytes
24 files changed, 734 insertions, 114 deletions
diff --git a/.clangd b/.clangd
new file mode 100644
index 0000000..e604f63
--- /dev/null
+++ b/.clangd
@@ -0,0 +1,3 @@
+CompileFlags:
+ Add:
+ - -I./vendor/raylib-6.0_linux_amd64/include
diff --git a/Makefile b/Makefile
index 150f3b5..67decc8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,31 +1,34 @@
OS := $(shell uname)
ifeq ($(OS), Linux)
-SYSTEM = linux_amd64
+ SYSTEM = linux_amd64
else ifeq ($(OS), Darwin)
-SYSTEM = macos
+ SYSTEM = macos
else ifeq ($(OS), WindowsNT)
-SYSTEM = windows
+ SYSTEM = windows
else
-SYSTEM = unknown
+ SYSTEM = unknown
endif
CC := clang
RAYLIB_VER := raylib-6.0_$(SYSTEM)
CFLAGS := -std=c99 -v -g -I./vendor/$(RAYLIB_VER)/include
-LDFLAGS := -L./vendor/$(RAYLIB_VER)/lib -Wl,-Bstatic -lraylib -Wl,-Bdynamic -lm -lpthread -ldl -lrt -lX11
+LDFLAGS := ./vendor/$(RAYLIB_VER)/lib/libraylib.a -lm
GAME := bin/stalag
HEXDUMP := bin/hexdump
-SOURCES := main.c
+SOURCES := main.c map.c game.c
+
+ifeq ($(SYSTEM), linux_amd64)
+ LDFLAGS += -lX11
+endif
-# Check if macOS and then append proper CFLAGS.
ifeq ($(SYSTEM), macos)
-CFLAGS += -framework CoreVideo -framework IOKit -framework Cocoa -framework GLUT -framework OpenGL
+ LDFLAGS += -framework CoreVideo -framework IOKit -framework Cocoa -framework GLUT -framework OpenGL
endif
all: info mkdirs $(HEXDUMP) $(GAME)
.PHONY: info mkdirs clean
-
+
info: # Print out information about the build
$(info CC : $(CC))
$(info SYSTEM : $(SYSTEM))
@@ -43,4 +46,4 @@ mkdirs:
mkdir -p bin
clean:
- -rm $(GAME) $(HEXDUMP) \ No newline at end of file
+ -rm $(GAME) $(HEXDUMP)
diff --git a/all.h b/all.h
new file mode 100644
index 0000000..40a19f1
--- /dev/null
+++ b/all.h
@@ -0,0 +1,117 @@
+#ifndef ALL_H
+#define ALL_H
+
+#include "raylib.h"
+#include "raymath.h"
+#include "rlgl.h"
+#include "config.h"
+
+// Resolve conflicts between Raylib and nonstd.h
+#define Color NS_Color
+#include "libraries/nonstd.h"
+#undef Color
+
+#include "libraries/vfs.h"
+#include <stdbool.h>
+#include <stddef.h>
+
+// --- Map Structures ---
+
+typedef struct {
+ Vector3 p[3];
+ char texture[64];
+ float shift[2];
+ float rotate;
+ float scale[2];
+} MapPlane;
+
+typedef struct {
+ MapPlane *planes;
+ int plane_count;
+} MapBrush;
+
+typedef struct {
+ char key[64];
+ char value[256];
+} MapProperty;
+
+typedef struct {
+ MapProperty *properties;
+ int property_count;
+ MapBrush *brushes;
+ int brush_count;
+} MapEntity;
+
+typedef struct {
+ MapEntity *entities;
+ int entity_count;
+} Map;
+
+typedef struct {
+ const char *data;
+ size_t length;
+ size_t pos;
+} MapParser;
+
+typedef struct {
+ Vector3 normal;
+ float dist;
+} Plane;
+
+typedef struct {
+ Vector3 *verts;
+ int count;
+} Polygon;
+
+typedef struct {
+ char texture[64];
+ array(float) vertices;
+ array(float) texcoords;
+ array(float) normals;
+} TextureGroup;
+
+typedef struct {
+ char name[64];
+ Texture2D tex;
+} CachedTexture;
+
+// --- Game State ---
+
+typedef struct {
+ Camera camera;
+ Model *world_models;
+ int world_model_count;
+ bool cursor_captured;
+ bool vsync;
+} GameState;
+
+extern GameState game;
+
+// --- Prototypes ---
+
+// Map
+char map_peek(MapParser *p);
+char map_get(MapParser *p);
+void map_skip_whitespace(MapParser *p);
+bool map_expect(MapParser *p, char expected);
+void map_parse_token(MapParser *p, char *buffer, int size);
+Vector3 map_parse_vector(MapParser *p);
+Map ParseMap(const char *filename);
+void FreeMap(Map map);
+
+// Geometry
+Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3);
+void PolyFree(Polygon p);
+Polygon PolyClip(Polygon poly, Plane plane);
+Polygon CreateLargeQuad(Plane plane);
+Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane);
+
+// Game
+Texture2D GetTexture(const char *name);
+void InitGame(void);
+void UpdateGame(void);
+void DrawGame(void);
+bool LoadMap(const char *filename);
+void UnloadMap(void);
+
+#endif
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..28d4604
--- /dev/null
+++ b/config.h
@@ -0,0 +1,8 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#define PLAYER_MOVE_SPEED 400.0f
+#define PLAYER_ROTATION_SPEED 0.05f
+#define PLAYER_MOUSE_SENSITIVITY 0.003f
+
+#endif
diff --git a/game.c b/game.c
new file mode 100644
index 0000000..db04ff7
--- /dev/null
+++ b/game.c
@@ -0,0 +1,306 @@
+#include "all.h"
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+GameState game;
+static array(CachedTexture) texture_cache;
+
+Texture2D GetTexture(const char *name) {
+ for (int i = 0; i < texture_cache.length; i++) {
+ if (strcmp(texture_cache.data[i].name, name) == 0) return texture_cache.data[i].tex;
+ }
+
+ char lname[256];
+ strncpy(lname, name, sizeof(lname));
+ for (int i = 0; lname[i]; i++) lname[i] = tolower(lname[i]);
+
+ char path[256];
+ void *data = NULL;
+ size_t size = 0;
+ const char *ext = ".jpg";
+
+ snprintf(path, sizeof(path), "textures/%s.png", lname);
+ data = vfs_read(path, &size);
+ if (data) {
+ ext = ".png";
+ } else {
+ snprintf(path, sizeof(path), "textures/%s.jpg", lname);
+ data = vfs_read(path, &size);
+ ext = ".jpg";
+ }
+
+ Texture2D tex = { 0 };
+ if (data) {
+ Image img = LoadImageFromMemory(ext, data, (int)size);
+ if (img.data) {
+ tex = LoadTextureFromImage(img);
+ UnloadImage(img);
+ }
+ vfs_free(data);
+ }
+
+ if (tex.id == 0) {
+ TraceLog(LOG_WARNING, "Failed to load texture: '%s' (tried path: '%s')", name, path);
+ Image img = GenImageChecked(64, 64, 8, 8, (Color){128, 128, 128, 255}, (Color){200, 200, 200, 255});
+ tex = LoadTextureFromImage(img);
+ UnloadImage(img);
+ }
+
+ if (tex.id != 0) {
+ GenTextureMipmaps(&tex);
+ SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
+ SetTextureWrap(tex, TEXTURE_WRAP_REPEAT);
+
+ CachedTexture cached;
+ strncpy(cached.name, name, sizeof(cached.name));
+ cached.tex = tex;
+ array_push(texture_cache, cached);
+ }
+
+ return tex;
+}
+
+void UnloadMap(void) {
+ if (game.world_models) {
+ for (int i = 0; i < game.world_model_count; i++) {
+ UnloadModel(game.world_models[i]);
+ }
+ free(game.world_models);
+ game.world_models = NULL;
+ game.world_model_count = 0;
+ }
+
+ for (int i = 0; i < texture_cache.length; i++) {
+ UnloadTexture(texture_cache.data[i].tex);
+ }
+ array_clear(texture_cache);
+}
+
+bool LoadMap(const char *filename) {
+ TraceLog(LOG_INFO, "Loading map: %s", filename);
+
+ Map map = ParseMap(filename);
+ if (map.entity_count == 0) {
+ TraceLog(LOG_ERROR, "Failed to load map or map is empty: %s", filename);
+ return false;
+ }
+
+ UnloadMap();
+
+ array(TextureGroup) groups;
+ array_init(groups);
+
+ // Default camera if no start found
+ game.camera.position = (Vector3){ 0, 10, 0 };
+ game.camera.target = (Vector3){ 1, 10, 0 };
+ game.camera.up = (Vector3){ 0, 1, 0 };
+ game.camera.fovy = 75.0f;
+ game.camera.projection = CAMERA_PERSPECTIVE;
+
+ int total_brushes = 0;
+ for (int i = 0; i < map.entity_count; i++) {
+ MapEntity *e = &map.entities[i];
+ bool is_world = false;
+ const char *classname = "";
+ for (int j = 0; j < e->property_count; j++) {
+ if (strcmp(e->properties[j].key, "classname") == 0) {
+ classname = e->properties[j].value;
+ if (strcmp(classname, "worldspawn") == 0) is_world = true;
+ }
+ if (strcmp(e->properties[j].key, "origin") == 0) {
+ float x, y, z;
+ sscanf(e->properties[j].value, "%f %f %f", &x, &y, &z);
+ if (strcmp(classname, "info_player_start") == 0) {
+ game.camera.position = (Vector3){ x, z, -y };
+ game.camera.target = Vector3Add(game.camera.position, (Vector3){0, 0, 1});
+ TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f", game.camera.position.x, game.camera.position.y, game.camera.position.z);
+ }
+ }
+ }
+
+ if (is_world) {
+ total_brushes += e->brush_count;
+ for (int j = 0; j < e->brush_count; j++) {
+ MapBrush *brush = &e->brushes[j];
+ for (int p_idx = 0; p_idx < brush->plane_count; p_idx++) {
+ MapPlane *mp = &brush->planes[p_idx];
+ Plane plane = PlaneFromPoints(mp->p[0], mp->p[1], mp->p[2]);
+
+ if (Vector3Length(plane.normal) < 0.5f) continue;
+
+ Polygon poly = CreateLargeQuad(plane);
+ for (int k = 0; k < brush->plane_count; k++) {
+ if (p_idx == k) continue;
+ Plane clipPlane = PlaneFromPoints(brush->planes[k].p[0], brush->planes[k].p[1], brush->planes[k].p[2]);
+ if (Vector3Length(clipPlane.normal) < 0.5f) continue;
+
+ Polygon next = PolyClip(poly, clipPlane);
+ PolyFree(poly);
+ poly = next;
+ }
+ if (poly.count >= 3) {
+ TextureGroup *group = NULL;
+ for (int g = 0; g < groups.length; g++) {
+ if (strcmp(groups.data[g].texture, mp->texture) == 0) {
+ group = &groups.data[g];
+ break;
+ }
+ }
+ if (!group) {
+ TextureGroup new_group = { 0 };
+ strncpy(new_group.texture, mp->texture, sizeof(new_group.texture));
+ array_init(new_group.vertices);
+ array_init(new_group.texcoords);
+ array_init(new_group.normals);
+ array_push(groups, new_group);
+ group = &groups.data[groups.length - 1];
+ }
+ for (int v = 1; v < poly.count - 1; v++) {
+ Vector3 v0 = poly.verts[0];
+ Vector3 v1 = poly.verts[v+1];
+ Vector3 v2 = poly.verts[v];
+ array_push(group->vertices, v0.x); array_push(group->vertices, v0.y); array_push(group->vertices, v0.z);
+ array_push(group->vertices, v1.x); array_push(group->vertices, v1.y); array_push(group->vertices, v1.z);
+ array_push(group->vertices, v2.x); array_push(group->vertices, v2.y); array_push(group->vertices, v2.z);
+ Vector2 uv0 = GetUV(v0, mp, plane);
+ Vector2 uv1 = GetUV(v1, mp, plane);
+ Vector2 uv2 = GetUV(v2, mp, plane);
+ array_push(group->texcoords, uv0.x); array_push(group->texcoords, uv0.y);
+ array_push(group->texcoords, uv1.x); array_push(group->texcoords, uv1.y);
+ array_push(group->texcoords, uv2.x); array_push(group->texcoords, uv2.y);
+ Vector3 out_normal = Vector3Scale(plane.normal, -1.0f);
+ array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
+ array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
+ array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
+ }
+ }
+ PolyFree(poly);
+ }
+ }
+ }
+ }
+
+ array(Model) final_models;
+ array_init(final_models);
+
+ for (int i = 0; i < groups.length; i++) {
+ TextureGroup *g = &groups.data[i];
+ if (g->vertices.length == 0) continue;
+ Mesh mesh = { 0 };
+ mesh.vertexCount = (int)g->vertices.length / 3;
+ mesh.triangleCount = mesh.vertexCount / 3;
+ mesh.vertices = (float *)malloc(g->vertices.length * sizeof(float));
+ memcpy(mesh.vertices, g->vertices.data, g->vertices.length * sizeof(float));
+ mesh.texcoords = (float *)malloc(g->texcoords.length * sizeof(float));
+ memcpy(mesh.texcoords, g->texcoords.data, g->texcoords.length * sizeof(float));
+ mesh.normals = (float *)malloc(g->normals.length * sizeof(float));
+ memcpy(mesh.normals, g->normals.data, g->normals.length * sizeof(float));
+ UploadMesh(&mesh, false);
+ Model model = LoadModelFromMesh(mesh);
+ model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = GetTexture(g->texture);
+ array_push(final_models, model);
+ array_free(g->vertices);
+ array_free(g->texcoords);
+ array_free(g->normals);
+ }
+
+ game.world_models = final_models.data;
+ game.world_model_count = (int)final_models.length;
+ array_free(groups);
+
+ FreeMap(map);
+ TraceLog(LOG_INFO, "Processed %d brushes into %d models", total_brushes, game.world_model_count);
+ return true;
+}
+
+void InitGame(void) {
+ array_init(texture_cache);
+ game.world_models = NULL;
+ game.world_model_count = 0;
+
+ LoadMap("maps/demo1.map");
+
+ game.cursor_captured = true;
+ DisableCursor();
+}
+
+void UpdateGame(void) {
+ float dt = GetFrameTime();
+
+ if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) {
+ game.cursor_captured = !game.cursor_captured;
+ if (game.cursor_captured) DisableCursor();
+ else EnableCursor();
+ }
+
+ if (IsKeyPressed(KEY_ONE)) LoadMap("maps/demo1.map");
+ if (IsKeyPressed(KEY_TWO)) LoadMap("maps/demo2.map");
+
+ if (IsKeyPressed(KEY_V)) {
+ game.vsync = !game.vsync;
+ if (game.vsync) {
+ SetWindowState(FLAG_VSYNC_HINT);
+ SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor()));
+ } else {
+ ClearWindowState(FLAG_VSYNC_HINT);
+ SetTargetFPS(0);
+ }
+ }
+
+ // Manual first-person movement
+ Vector3 forward = Vector3Normalize(Vector3Subtract(game.camera.target, game.camera.position));
+ Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, game.camera.up));
+
+ Vector3 move = { 0 };
+ if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward);
+ if (IsKeyDown(KEY_S)) move = Vector3Subtract(move, forward);
+ if (IsKeyDown(KEY_D)) move = Vector3Add(move, right);
+ if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right);
+
+ if (Vector3Length(move) > 0) {
+ move = Vector3Scale(Vector3Normalize(move), PLAYER_MOVE_SPEED * dt);
+ game.camera.position = Vector3Add(game.camera.position, move);
+ game.camera.target = Vector3Add(game.camera.target, move);
+ }
+
+ if (game.cursor_captured) {
+ // Manual rotation
+ Vector2 mouseDelta = GetMouseDelta();
+ float yaw = -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY;
+ float pitch = -mouseDelta.y * PLAYER_MOUSE_SENSITIVITY;
+
+ Vector3 view = Vector3Subtract(game.camera.target, game.camera.position);
+
+ // Yaw
+ view = Vector3RotateByAxisAngle(view, game.camera.up, yaw);
+
+ // Pitch
+ Vector3 axis = Vector3Normalize(Vector3CrossProduct(view, game.camera.up));
+ view = Vector3RotateByAxisAngle(view, axis, pitch);
+
+ game.camera.target = Vector3Add(game.camera.position, view);
+ }
+}
+
+void DrawGame(void) {
+ BeginDrawing();
+ ClearBackground(DARKGRAY);
+ BeginMode3D(game.camera);
+
+ // Enable backface culling to hide interior faces of brushes
+ rlEnableBackfaceCulling();
+ for (int i = 0; i < game.world_model_count; i++) {
+ DrawModel(game.world_models[i], (Vector3){ 0, 0, 0 }, 1.0f, WHITE);
+ }
+
+ EndMode3D();
+
+ int screenWidth = GetScreenWidth();
+ int screenHeight = GetScreenHeight();
+ DrawLine(screenWidth / 2 - 10, screenHeight / 2, screenWidth / 2 + 10, screenHeight / 2, GREEN);
+ DrawLine(screenWidth / 2, screenHeight / 2 - 10, screenWidth / 2, screenHeight / 2 + 10, GREEN);
+ DrawFPS(10, 10);
+ DrawText(TextFormat("VSync: %s", game.vsync ? "ON" : "OFF"), 10, 30, 20, GREEN);
+ EndDrawing();
+}
diff --git a/libraries/nonstd.h b/libraries/nonstd.h
index 531834d..f1848d3 100644
--- a/libraries/nonstd.h
+++ b/libraries/nonstd.h
@@ -77,12 +77,12 @@ NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count);
#define UNUSED(value) (void)(value)
#define TODO(message) \
do { \
- fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); \
+ TraceLog(LOG_ERROR, "%s:%d: TODO: %s", __FILE__, __LINE__, message); \
abort(); \
} while (0)
#define UNREACHABLE(message) \
do { \
- fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); \
+ TraceLog(LOG_ERROR, "%s:%d: UNREACHABLE: %s", __FILE__, __LINE__, message); \
abort(); \
} while (0)
@@ -315,29 +315,6 @@ NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
-// Logging
-typedef enum {
- LOG_ERROR,
- LOG_WARN,
- LOG_INFO,
- LOG_DEBUG,
-} LogLevel;
-
-NONSTD_DEF void set_log_level(LogLevel level);
-NONSTD_DEF LogLevel get_log_level_from_env(void);
-NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...);
-
-#define LOG_INFO_MSG(...) log_message(stdout, LOG_INFO, __VA_ARGS__)
-#define LOG_DEBUG_MSG(...) log_message(stdout, LOG_DEBUG, __VA_ARGS__)
-#define LOG_WARN_MSG(...) log_message(stderr, LOG_WARN, __VA_ARGS__)
-#define LOG_ERROR_MSG(...) log_message(stderr, LOG_ERROR, __VA_ARGS__)
-
-#define COLOR_RESET "\033[0m"
-#define COLOR_INFO "\033[32m"
-#define COLOR_DEBUG "\033[36m"
-#define COLOR_WARNING "\033[33m"
-#define COLOR_ERROR "\033[31m"
-
#ifdef NONSTD_IMPLEMENTATION
NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) {
@@ -587,66 +564,6 @@ NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) {
return write_entire_file(filepath, sb->data, sb->length);
}
-// Logging Implementation
-
-static LogLevel max_level = LOG_INFO;
-
-static const char *level_strings[] = {
- "ERROR",
- "WARN",
- "INFO",
- "DEBUG",
-};
-
-static const char *level_colors[] = {
- COLOR_ERROR,
- COLOR_WARNING,
- COLOR_INFO,
- COLOR_DEBUG,
-};
-
-NONSTD_DEF void set_log_level(LogLevel level) {
- max_level = level;
-}
-
-NONSTD_DEF LogLevel get_log_level_from_env(void) {
- const char *env = getenv("LOG_LEVEL");
- if (env) {
- int level = atoi(env);
- if (level >= 0 && level <= 3) {
- return (LogLevel)level;
- }
- }
-
- return max_level;
-}
-
-NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...) {
- if (max_level < level)
- return;
-
- struct timeval tv;
- gettimeofday(&tv, NULL);
- struct tm *tm_info = localtime(&tv.tv_sec);
-
- char time_str[24];
- strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
-
- const char *color = isatty(fileno(stream)) ? level_colors[level] : "";
- const char *reset = isatty(fileno(stream)) ? COLOR_RESET : "";
-
- const char *log_format = "%s[%s.%03d] [%-5s] ";
- fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000), level_strings[level]);
-
- va_list args;
- va_start(args, format);
- vfprintf(stream, format, args);
- va_end(args);
-
- fprintf(stream, "%s\n", reset);
- fflush(stream);
-}
-
// PPM Image Implementation
NONSTD_DEF Canvas ppm_init(u32 width, u32 height) {
diff --git a/libraries/vfs.h b/libraries/vfs.h
index 6a243cc..6017407 100644
--- a/libraries/vfs.h
+++ b/libraries/vfs.h
@@ -57,28 +57,28 @@ void vfs_init(const char* pak_path) {
const char* debug_env = getenv("DEBUG");
if (debug_env && (strcmp(debug_env, "1") == 0 || strcmp(debug_env, "true") == 0)) {
g_disk_mode = 1;
- log_message(stdout, LOG_INFO, "VFS: Operating in Disk Mode (DEBUG enabled)");
+ TraceLog(LOG_INFO, "VFS: Operating in Disk Mode (DEBUG enabled)");
return;
}
FILE* f = fopen(pak_path, "rb");
if (!f) {
- log_message(stderr, LOG_ERROR, "VFS: Failed to open pak file: %s (Error: %s)", pak_path, strerror(errno));
- log_message(stderr, LOG_WARN, "VFS: Falling back to Disk Mode");
+ TraceLog(LOG_ERROR, "VFS: Failed to open pak file: %s (Error: %s)", pak_path, strerror(errno));
+ TraceLog(LOG_WARNING, "VFS: Falling back to Disk Mode");
g_disk_mode = 1;
return;
}
VfsHeader header;
if (fread(&header, sizeof(VfsHeader), 1, f) != 1) {
- log_message(stderr, LOG_ERROR, "VFS: Failed to read header from %s", pak_path);
+ TraceLog(LOG_ERROR, "VFS: Failed to read header from %s", pak_path);
fclose(f);
g_disk_mode = 1;
return;
}
if (memcmp(header.magic, "DRP1", 4) != 0) {
- log_message(stderr, LOG_ERROR, "VFS: Invalid magic in %s", pak_path);
+ TraceLog(LOG_ERROR, "VFS: Invalid magic in %s", pak_path);
fclose(f);
g_disk_mode = 1;
return;
@@ -87,7 +87,7 @@ void vfs_init(const char* pak_path) {
g_num_entries = header.num_files;
g_entries = (VfsEntry*)malloc(sizeof(VfsEntry) * g_num_entries);
if (fread(g_entries, sizeof(VfsEntry), g_num_entries, f) != g_num_entries) {
- log_message(stderr, LOG_ERROR, "VFS: Failed to read index table from %s", pak_path);
+ TraceLog(LOG_ERROR, "VFS: Failed to read index table from %s", pak_path);
free(g_entries);
g_entries = NULL;
fclose(f);
@@ -97,7 +97,7 @@ void vfs_init(const char* pak_path) {
fclose(f);
strncpy(g_pak_path, pak_path, sizeof(g_pak_path) - 1);
- log_message(stdout, LOG_INFO, "VFS: Loaded %u files from %s", g_num_entries, pak_path);
+ TraceLog(LOG_INFO, "VFS: Loaded %u files from %s", g_num_entries, pak_path);
}
void vfs_shutdown(void) {
@@ -137,7 +137,7 @@ VfsFile* vfs_open(const char* path) {
}
}
- log_message(stderr, LOG_WARN, "VFS: File not found: %s", path);
+ TraceLog(LOG_WARNING, "VFS: File not found: %s", path);
return NULL;
}
diff --git a/main.c b/main.c
index 091c0f4..bcacb47 100644
--- a/main.c
+++ b/main.c
@@ -1,18 +1,24 @@
-#include "raylib.h"
+#define _POSIX_C_SOURCE 200809L
+#define NONSTD_IMPLEMENTATION
+#define VFS_IMPLEMENTATION
+#include "all.h"
-int main(void)
-{
- InitWindow(800, 450, "raylib example - basic window");
+int main(void) {
+ SetConfigFlags(FLAG_VSYNC_HINT);
+ InitWindow(1920, 1080, "Stalag");
+ SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor()));
- while (!WindowShouldClose())
- {
- BeginDrawing();
- ClearBackground(RAYWHITE);
- DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
- EndDrawing();
+ vfs_init("data.pak");
+ InitGame();
+ game.vsync = true;
+
+ while (!WindowShouldClose()) {
+ UpdateGame();
+ DrawGame();
}
+ vfs_shutdown();
CloseWindow();
return 0;
-} \ No newline at end of file
+}
diff --git a/map.c b/map.c
new file mode 100644
index 0000000..f28476f
--- /dev/null
+++ b/map.c
@@ -0,0 +1,246 @@
+#include "all.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+char map_peek(MapParser *p) {
+ if (p->pos >= p->length) return 0;
+ return p->data[p->pos];
+}
+
+char map_get(MapParser *p) {
+ if (p->pos >= p->length) return 0;
+ return p->data[p->pos++];
+}
+
+void map_skip_whitespace(MapParser *p) {
+ while (true) {
+ char c = map_peek(p);
+ if (isspace(c)) {
+ map_get(p);
+ } else if (c == '/' && p->data[p->pos + 1] == '/') {
+ while (map_peek(p) != '\n' && map_peek(p) != 0) map_get(p);
+ } else {
+ break;
+ }
+ }
+}
+
+bool map_expect(MapParser *p, char expected) {
+ map_skip_whitespace(p);
+ if (map_get(p) == expected) return true;
+ return false;
+}
+
+void map_parse_token(MapParser *p, char *buffer, int size) {
+ map_skip_whitespace(p);
+ int i = 0;
+ bool quoted = false;
+ if (map_peek(p) == '"') {
+ quoted = true;
+ map_get(p);
+ }
+
+ while (true) {
+ char c = map_peek(p);
+ if (c == 0) break;
+ if (quoted) {
+ if (c == '"') {
+ map_get(p);
+ break;
+ }
+ } else {
+ if (isspace(c) || c == '(' || c == ')' || c == '{' || c == '}') break;
+ }
+ if (i < size - 1) buffer[i++] = map_get(p);
+ else map_get(p);
+ }
+ buffer[i] = 0;
+}
+
+Vector3 map_parse_vector(MapParser *p) {
+ map_expect(p, '(');
+ char buf[64];
+ map_parse_token(p, buf, sizeof(buf));
+ float x = (float)atof(buf);
+ map_parse_token(p, buf, sizeof(buf));
+ float y = (float)atof(buf);
+ map_parse_token(p, buf, sizeof(buf));
+ float z = (float)atof(buf);
+ map_expect(p, ')');
+ return (Vector3){ x, z, -y };
+}
+
+Map ParseMap(const char *filename) {
+ size_t size;
+ char *data = read_entire_file(filename, &size);
+ Map map = { 0 };
+ if (!data) return map;
+
+ MapParser p = { data, size, 0 };
+ array(MapEntity) entities;
+ array_init(entities);
+
+ while (true) {
+ map_skip_whitespace(&p);
+ if (map_peek(&p) == 0) break;
+
+ if (map_expect(&p, '{')) {
+ MapEntity entity = { 0 };
+ array(MapProperty) props;
+ array(MapBrush) brushes;
+ array_init(props);
+ array_init(brushes);
+
+ while (true) {
+ map_skip_whitespace(&p);
+ char c = map_peek(&p);
+ if (c == '}') {
+ map_get(&p);
+ break;
+ } else if (c == '"') {
+ MapProperty prop;
+ map_parse_token(&p, prop.key, sizeof(prop.key));
+ map_parse_token(&p, prop.value, sizeof(prop.value));
+ array_push(props, prop);
+ } else if (c == '{') {
+ map_get(&p);
+ MapBrush brush = { 0 };
+ array(MapPlane) planes;
+ array_init(planes);
+ while (true) {
+ map_skip_whitespace(&p);
+ if (map_peek(&p) == '}') {
+ map_get(&p);
+ break;
+ }
+ MapPlane plane;
+ plane.p[0] = map_parse_vector(&p);
+ plane.p[1] = map_parse_vector(&p);
+ plane.p[2] = map_parse_vector(&p);
+ map_parse_token(&p, plane.texture, sizeof(plane.texture));
+
+ char buf[64];
+ map_parse_token(&p, buf, sizeof(buf)); plane.shift[0] = (float)atof(buf);
+ map_parse_token(&p, buf, sizeof(buf)); plane.shift[1] = (float)atof(buf);
+ map_parse_token(&p, buf, sizeof(buf)); plane.rotate = (float)atof(buf);
+ map_parse_token(&p, buf, sizeof(buf)); plane.scale[0] = (float)atof(buf);
+ map_parse_token(&p, buf, sizeof(buf)); plane.scale[1] = (float)atof(buf);
+
+ array_push(planes, plane);
+ }
+ brush.planes = planes.data;
+ brush.plane_count = (int)planes.length;
+ array_push(brushes, brush);
+ } else {
+ map_get(&p);
+ }
+ }
+ entity.properties = props.data;
+ entity.property_count = (int)props.length;
+ entity.brushes = brushes.data;
+ entity.brush_count = (int)brushes.length;
+ array_push(entities, entity);
+ }
+ }
+
+ map.entities = entities.data;
+ map.entity_count = (int)entities.length;
+ free(data);
+ return map;
+}
+
+void FreeMap(Map map) {
+ for (int i = 0; i < map.entity_count; i++) {
+ MapEntity *e = &map.entities[i];
+ if (e->properties) free(e->properties);
+ for (int j = 0; j < e->brush_count; j++) {
+ if (e->brushes[j].planes) free(e->brushes[j].planes);
+ }
+ if (e->brushes) free(e->brushes);
+ }
+ if (map.entities) free(map.entities);
+}
+
+Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3) {
+ Vector3 v1 = Vector3Subtract(p2, p1);
+ Vector3 v2 = Vector3Subtract(p3, p1);
+ Vector3 normal = Vector3Normalize(Vector3CrossProduct(v1, v2));
+ return (Plane){ normal, Vector3DotProduct(normal, p1) };
+}
+
+void PolyFree(Polygon p) {
+ if (p.verts) free(p.verts);
+}
+
+Polygon PolyClip(Polygon poly, Plane plane) {
+ if (poly.count == 0) return poly;
+
+ array(Vector3) out_verts;
+ array_init(out_verts);
+
+ for (int i = 0; i < poly.count; i++) {
+ Vector3 p1 = poly.verts[i];
+ Vector3 p2 = poly.verts[(i + 1) % poly.count];
+
+ float d1 = Vector3DotProduct(plane.normal, p1) - plane.dist;
+ float d2 = Vector3DotProduct(plane.normal, p2) - plane.dist;
+
+ // Using a smaller epsilon and more robust logic
+ const float eps = 0.001f;
+
+ if (d1 >= -eps) {
+ if (d2 >= -eps) {
+ array_push(out_verts, p2);
+ } else {
+ float t = d1 / (d1 - d2);
+ Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t));
+ array_push(out_verts, intersect);
+ }
+ } else {
+ if (d2 >= -eps) {
+ float t = d1 / (d1 - d2);
+ Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t));
+ array_push(out_verts, intersect);
+ array_push(out_verts, p2);
+ }
+ }
+ }
+
+ Polygon res;
+ res.verts = out_verts.data;
+ res.count = (int)out_verts.length;
+ return res;
+}
+
+Polygon CreateLargeQuad(Plane plane) {
+ Vector3 n = plane.normal;
+ Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 };
+ Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n));
+ up = Vector3CrossProduct(n, right);
+
+ Vector3 center = Vector3Scale(n, plane.dist);
+ float size = 10000.0f;
+
+ Polygon poly;
+ poly.verts = ALLOC(Vector3, 4);
+ poly.count = 4;
+ poly.verts[0] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, size)));
+ poly.verts[1] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, size)));
+ poly.verts[2] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, -size)));
+ poly.verts[3] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, -size)));
+
+ return poly;
+}
+
+Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane) {
+ Vector3 n = plane.normal;
+ Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 };
+ Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n));
+ up = Vector3CrossProduct(n, right);
+
+ float u = Vector3DotProduct(p, right) * (1.0f / (mp->scale[0] ? mp->scale[0] : 1.0f)) + mp->shift[0];
+ float v = Vector3DotProduct(p, up) * (1.0f / (mp->scale[1] ? mp->scale[1] : 1.0f)) + mp->shift[1];
+
+ return (Vector2){ u / 64.0f, v / 64.0f };
+}
diff --git a/maps/demo1.map b/maps/demo1.map
index 687c93c..e2e286f 100644
--- a/maps/demo1.map
+++ b/maps/demo1.map
@@ -30,4 +30,18 @@
( -16 336 16 ) ( -15 336 16 ) ( -16 336 17 ) environment/planks_012 0 0 0 1 1
( -16 112 16 ) ( -16 112 17 ) ( -16 113 16 ) environment/planks_012 16 0 0 1 1
}
+// brush 3
+{
+( -16 272 0 ) ( -16 273 0 ) ( -16 272 1 ) environment/planks_012 0 0 0 1 1
+( -16 272 0 ) ( -16 272 1 ) ( -15 272 0 ) environment/planks_012 0 0 0 1 1
+( -16 272 0 ) ( -15 272 0 ) ( -16 273 0 ) environment/planks_012 0 0 0 1 1
+( 80 336 64 ) ( 80 337 64 ) ( 81 336 64 ) environment/planks_012 0 0 0 1 1
+( 80 336 16 ) ( 81 336 16 ) ( 80 336 17 ) environment/planks_012 0 0 0 1 1
+( 80 336 16 ) ( 80 336 17 ) ( 80 337 16 ) environment/planks_012 0 0 0 1 1
+}
+}
+// entity 1
+{
+"classname" "info_player_start"
+"origin" "-112 -16 40"
}
diff --git a/textures/brushes/bricks_076c.jpg b/textures/brushes/bricks_076c.jpg
deleted file mode 100644
index b4be330..0000000
--- a/textures/brushes/bricks_076c.jpg
+++ /dev/null
Binary files differ
diff --git a/textures/brushes/bricks_076c.png b/textures/brushes/bricks_076c.png
new file mode 100644
index 0000000..d9b69dc
--- /dev/null
+++ b/textures/brushes/bricks_076c.png
Binary files differ
diff --git a/textures/brushes/ground_068.jpg b/textures/brushes/ground_068.jpg
deleted file mode 100644
index beb7d2e..0000000
--- a/textures/brushes/ground_068.jpg
+++ /dev/null
Binary files differ
diff --git a/textures/brushes/ground_068.png b/textures/brushes/ground_068.png
new file mode 100644
index 0000000..cfaed17
--- /dev/null
+++ b/textures/brushes/ground_068.png
Binary files differ
diff --git a/textures/brushes/rock_016.jpg b/textures/brushes/rock_016.jpg
deleted file mode 100644
index c6986aa..0000000
--- a/textures/brushes/rock_016.jpg
+++ /dev/null
Binary files differ
diff --git a/textures/brushes/rock_016.png b/textures/brushes/rock_016.png
new file mode 100644
index 0000000..9c6ade8
--- /dev/null
+++ b/textures/brushes/rock_016.png
Binary files differ
diff --git a/textures/brushes/rock_030.jpg b/textures/brushes/rock_030.jpg
deleted file mode 100644
index 6604eb9..0000000
--- a/textures/brushes/rock_030.jpg
+++ /dev/null
Binary files differ
diff --git a/textures/brushes/rock_030.png b/textures/brushes/rock_030.png
new file mode 100644
index 0000000..5ed08cb
--- /dev/null
+++ b/textures/brushes/rock_030.png
Binary files differ
diff --git a/textures/environment/paintedwood_008a.jpg b/textures/environment/paintedwood_008a.jpg
deleted file mode 100644
index 2ab38f0..0000000
--- a/textures/environment/paintedwood_008a.jpg
+++ /dev/null
Binary files differ
diff --git a/textures/environment/paintedwood_008a.png b/textures/environment/paintedwood_008a.png
new file mode 100644
index 0000000..bc1d468
--- /dev/null
+++ b/textures/environment/paintedwood_008a.png
Binary files differ
diff --git a/textures/environment/planks_012.jpg b/textures/environment/planks_012.jpg
deleted file mode 100644
index 30c21a2..0000000
--- a/textures/environment/planks_012.jpg
+++ /dev/null
Binary files differ
diff --git a/textures/environment/planks_012.png b/textures/environment/planks_012.png
new file mode 100644
index 0000000..ddbbe89
--- /dev/null
+++ b/textures/environment/planks_012.png
Binary files differ
diff --git a/textures/environment/roofingtiles_012b.jpg b/textures/environment/roofingtiles_012b.jpg
deleted file mode 100644
index 47689fb..0000000
--- a/textures/environment/roofingtiles_012b.jpg
+++ /dev/null
Binary files differ
diff --git a/textures/environment/roofingtiles_012b.png b/textures/environment/roofingtiles_012b.png
new file mode 100644
index 0000000..2040b86
--- /dev/null
+++ b/textures/environment/roofingtiles_012b.png
Binary files differ