From 63eb46698b1e19d3f36944992b948c54a7a3740b Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Tue, 28 Apr 2026 07:50:31 +0200 Subject: Compiler settings and macOS port --- .clangd | 3 + Makefile | 23 ++- all.h | 117 +++++++++++ config.h | 8 + game.c | 306 +++++++++++++++++++++++++++++ libraries/nonstd.h | 87 +------- libraries/vfs.h | 16 +- main.c | 28 +-- map.c | 246 +++++++++++++++++++++++ maps/demo1.map | 14 ++ textures/brushes/bricks_076c.jpg | Bin 96128 -> 0 bytes textures/brushes/bricks_076c.png | Bin 0 -> 111850 bytes textures/brushes/ground_068.jpg | Bin 129649 -> 0 bytes textures/brushes/ground_068.png | Bin 0 -> 129984 bytes textures/brushes/rock_016.jpg | Bin 57831 -> 0 bytes textures/brushes/rock_016.png | Bin 0 -> 97693 bytes textures/brushes/rock_030.jpg | Bin 88953 -> 0 bytes textures/brushes/rock_030.png | Bin 0 -> 123792 bytes textures/environment/paintedwood_008a.jpg | Bin 362255 -> 0 bytes textures/environment/paintedwood_008a.png | Bin 0 -> 477538 bytes textures/environment/planks_012.jpg | Bin 346294 -> 0 bytes textures/environment/planks_012.png | Bin 0 -> 422134 bytes textures/environment/roofingtiles_012b.jpg | Bin 263430 -> 0 bytes textures/environment/roofingtiles_012b.png | Bin 0 -> 370240 bytes 24 files changed, 734 insertions(+), 114 deletions(-) create mode 100644 .clangd create mode 100644 all.h create mode 100644 config.h create mode 100644 game.c create mode 100644 map.c delete mode 100644 textures/brushes/bricks_076c.jpg create mode 100644 textures/brushes/bricks_076c.png delete mode 100644 textures/brushes/ground_068.jpg create mode 100644 textures/brushes/ground_068.png delete mode 100644 textures/brushes/rock_016.jpg create mode 100644 textures/brushes/rock_016.png delete mode 100644 textures/brushes/rock_030.jpg create mode 100644 textures/brushes/rock_030.png delete mode 100644 textures/environment/paintedwood_008a.jpg create mode 100644 textures/environment/paintedwood_008a.png delete mode 100644 textures/environment/planks_012.jpg create mode 100644 textures/environment/planks_012.png delete mode 100644 textures/environment/roofingtiles_012b.jpg create mode 100644 textures/environment/roofingtiles_012b.png 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 +#include + +// --- 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 +#include +#include + +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 +#include +#include + +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 Binary files a/textures/brushes/bricks_076c.jpg and /dev/null differ diff --git a/textures/brushes/bricks_076c.png b/textures/brushes/bricks_076c.png new file mode 100644 index 0000000..d9b69dc Binary files /dev/null and b/textures/brushes/bricks_076c.png differ diff --git a/textures/brushes/ground_068.jpg b/textures/brushes/ground_068.jpg deleted file mode 100644 index beb7d2e..0000000 Binary files a/textures/brushes/ground_068.jpg and /dev/null differ diff --git a/textures/brushes/ground_068.png b/textures/brushes/ground_068.png new file mode 100644 index 0000000..cfaed17 Binary files /dev/null and b/textures/brushes/ground_068.png differ diff --git a/textures/brushes/rock_016.jpg b/textures/brushes/rock_016.jpg deleted file mode 100644 index c6986aa..0000000 Binary files a/textures/brushes/rock_016.jpg and /dev/null differ diff --git a/textures/brushes/rock_016.png b/textures/brushes/rock_016.png new file mode 100644 index 0000000..9c6ade8 Binary files /dev/null and b/textures/brushes/rock_016.png differ diff --git a/textures/brushes/rock_030.jpg b/textures/brushes/rock_030.jpg deleted file mode 100644 index 6604eb9..0000000 Binary files a/textures/brushes/rock_030.jpg and /dev/null differ diff --git a/textures/brushes/rock_030.png b/textures/brushes/rock_030.png new file mode 100644 index 0000000..5ed08cb Binary files /dev/null and b/textures/brushes/rock_030.png differ diff --git a/textures/environment/paintedwood_008a.jpg b/textures/environment/paintedwood_008a.jpg deleted file mode 100644 index 2ab38f0..0000000 Binary files a/textures/environment/paintedwood_008a.jpg and /dev/null differ diff --git a/textures/environment/paintedwood_008a.png b/textures/environment/paintedwood_008a.png new file mode 100644 index 0000000..bc1d468 Binary files /dev/null and b/textures/environment/paintedwood_008a.png differ diff --git a/textures/environment/planks_012.jpg b/textures/environment/planks_012.jpg deleted file mode 100644 index 30c21a2..0000000 Binary files a/textures/environment/planks_012.jpg and /dev/null differ diff --git a/textures/environment/planks_012.png b/textures/environment/planks_012.png new file mode 100644 index 0000000..ddbbe89 Binary files /dev/null and b/textures/environment/planks_012.png differ diff --git a/textures/environment/roofingtiles_012b.jpg b/textures/environment/roofingtiles_012b.jpg deleted file mode 100644 index 47689fb..0000000 Binary files a/textures/environment/roofingtiles_012b.jpg and /dev/null differ diff --git a/textures/environment/roofingtiles_012b.png b/textures/environment/roofingtiles_012b.png new file mode 100644 index 0000000..2040b86 Binary files /dev/null and b/textures/environment/roofingtiles_012b.png differ -- cgit v1.2.3