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 @@
1CompileFlags:
2 Add:
3 - -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 @@
1OS := $(shell uname) 1OS := $(shell uname)
2ifeq ($(OS), Linux) 2ifeq ($(OS), Linux)
3SYSTEM = linux_amd64 3 SYSTEM = linux_amd64
4else ifeq ($(OS), Darwin) 4else ifeq ($(OS), Darwin)
5SYSTEM = macos 5 SYSTEM = macos
6else ifeq ($(OS), WindowsNT) 6else ifeq ($(OS), WindowsNT)
7SYSTEM = windows 7 SYSTEM = windows
8else 8else
9SYSTEM = unknown 9 SYSTEM = unknown
10endif 10endif
11 11
12CC := clang 12CC := clang
13RAYLIB_VER := raylib-6.0_$(SYSTEM) 13RAYLIB_VER := raylib-6.0_$(SYSTEM)
14CFLAGS := -std=c99 -v -g -I./vendor/$(RAYLIB_VER)/include 14CFLAGS := -std=c99 -v -g -I./vendor/$(RAYLIB_VER)/include
15LDFLAGS := -L./vendor/$(RAYLIB_VER)/lib -Wl,-Bstatic -lraylib -Wl,-Bdynamic -lm -lpthread -ldl -lrt -lX11 15LDFLAGS := ./vendor/$(RAYLIB_VER)/lib/libraylib.a -lm
16GAME := bin/stalag 16GAME := bin/stalag
17HEXDUMP := bin/hexdump 17HEXDUMP := bin/hexdump
18SOURCES := main.c 18SOURCES := main.c map.c game.c
19
20ifeq ($(SYSTEM), linux_amd64)
21 LDFLAGS += -lX11
22endif
19 23
20# Check if macOS and then append proper CFLAGS.
21ifeq ($(SYSTEM), macos) 24ifeq ($(SYSTEM), macos)
22CFLAGS += -framework CoreVideo -framework IOKit -framework Cocoa -framework GLUT -framework OpenGL 25 LDFLAGS += -framework CoreVideo -framework IOKit -framework Cocoa -framework GLUT -framework OpenGL
23endif 26endif
24 27
25all: info mkdirs $(HEXDUMP) $(GAME) 28all: info mkdirs $(HEXDUMP) $(GAME)
26 29
27.PHONY: info mkdirs clean 30.PHONY: info mkdirs clean
28 31
29info: # Print out information about the build 32info: # Print out information about the build
30 $(info CC : $(CC)) 33 $(info CC : $(CC))
31 $(info SYSTEM : $(SYSTEM)) 34 $(info SYSTEM : $(SYSTEM))
@@ -43,4 +46,4 @@ mkdirs:
43 mkdir -p bin 46 mkdir -p bin
44 47
45clean: 48clean:
46 -rm $(GAME) $(HEXDUMP) \ No newline at end of file 49 -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 @@
1#ifndef ALL_H
2#define ALL_H
3
4#include "raylib.h"
5#include "raymath.h"
6#include "rlgl.h"
7#include "config.h"
8
9// Resolve conflicts between Raylib and nonstd.h
10#define Color NS_Color
11#include "libraries/nonstd.h"
12#undef Color
13
14#include "libraries/vfs.h"
15#include <stdbool.h>
16#include <stddef.h>
17
18// --- Map Structures ---
19
20typedef struct {
21 Vector3 p[3];
22 char texture[64];
23 float shift[2];
24 float rotate;
25 float scale[2];
26} MapPlane;
27
28typedef struct {
29 MapPlane *planes;
30 int plane_count;
31} MapBrush;
32
33typedef struct {
34 char key[64];
35 char value[256];
36} MapProperty;
37
38typedef struct {
39 MapProperty *properties;
40 int property_count;
41 MapBrush *brushes;
42 int brush_count;
43} MapEntity;
44
45typedef struct {
46 MapEntity *entities;
47 int entity_count;
48} Map;
49
50typedef struct {
51 const char *data;
52 size_t length;
53 size_t pos;
54} MapParser;
55
56typedef struct {
57 Vector3 normal;
58 float dist;
59} Plane;
60
61typedef struct {
62 Vector3 *verts;
63 int count;
64} Polygon;
65
66typedef struct {
67 char texture[64];
68 array(float) vertices;
69 array(float) texcoords;
70 array(float) normals;
71} TextureGroup;
72
73typedef struct {
74 char name[64];
75 Texture2D tex;
76} CachedTexture;
77
78// --- Game State ---
79
80typedef struct {
81 Camera camera;
82 Model *world_models;
83 int world_model_count;
84 bool cursor_captured;
85 bool vsync;
86} GameState;
87
88extern GameState game;
89
90// --- Prototypes ---
91
92// Map
93char map_peek(MapParser *p);
94char map_get(MapParser *p);
95void map_skip_whitespace(MapParser *p);
96bool map_expect(MapParser *p, char expected);
97void map_parse_token(MapParser *p, char *buffer, int size);
98Vector3 map_parse_vector(MapParser *p);
99Map ParseMap(const char *filename);
100void FreeMap(Map map);
101
102// Geometry
103Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3);
104void PolyFree(Polygon p);
105Polygon PolyClip(Polygon poly, Plane plane);
106Polygon CreateLargeQuad(Plane plane);
107Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane);
108
109// Game
110Texture2D GetTexture(const char *name);
111void InitGame(void);
112void UpdateGame(void);
113void DrawGame(void);
114bool LoadMap(const char *filename);
115void UnloadMap(void);
116
117#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 @@
1#ifndef CONFIG_H
2#define CONFIG_H
3
4#define PLAYER_MOVE_SPEED 400.0f
5#define PLAYER_ROTATION_SPEED 0.05f
6#define PLAYER_MOUSE_SENSITIVITY 0.003f
7
8#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 @@
1#include "all.h"
2#include <stdio.h>
3#include <string.h>
4#include <ctype.h>
5
6GameState game;
7static array(CachedTexture) texture_cache;
8
9Texture2D GetTexture(const char *name) {
10 for (int i = 0; i < texture_cache.length; i++) {
11 if (strcmp(texture_cache.data[i].name, name) == 0) return texture_cache.data[i].tex;
12 }
13
14 char lname[256];
15 strncpy(lname, name, sizeof(lname));
16 for (int i = 0; lname[i]; i++) lname[i] = tolower(lname[i]);
17
18 char path[256];
19 void *data = NULL;
20 size_t size = 0;
21 const char *ext = ".jpg";
22
23 snprintf(path, sizeof(path), "textures/%s.png", lname);
24 data = vfs_read(path, &size);
25 if (data) {
26 ext = ".png";
27 } else {
28 snprintf(path, sizeof(path), "textures/%s.jpg", lname);
29 data = vfs_read(path, &size);
30 ext = ".jpg";
31 }
32
33 Texture2D tex = { 0 };
34 if (data) {
35 Image img = LoadImageFromMemory(ext, data, (int)size);
36 if (img.data) {
37 tex = LoadTextureFromImage(img);
38 UnloadImage(img);
39 }
40 vfs_free(data);
41 }
42
43 if (tex.id == 0) {
44 TraceLog(LOG_WARNING, "Failed to load texture: '%s' (tried path: '%s')", name, path);
45 Image img = GenImageChecked(64, 64, 8, 8, (Color){128, 128, 128, 255}, (Color){200, 200, 200, 255});
46 tex = LoadTextureFromImage(img);
47 UnloadImage(img);
48 }
49
50 if (tex.id != 0) {
51 GenTextureMipmaps(&tex);
52 SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
53 SetTextureWrap(tex, TEXTURE_WRAP_REPEAT);
54
55 CachedTexture cached;
56 strncpy(cached.name, name, sizeof(cached.name));
57 cached.tex = tex;
58 array_push(texture_cache, cached);
59 }
60
61 return tex;
62}
63
64void UnloadMap(void) {
65 if (game.world_models) {
66 for (int i = 0; i < game.world_model_count; i++) {
67 UnloadModel(game.world_models[i]);
68 }
69 free(game.world_models);
70 game.world_models = NULL;
71 game.world_model_count = 0;
72 }
73
74 for (int i = 0; i < texture_cache.length; i++) {
75 UnloadTexture(texture_cache.data[i].tex);
76 }
77 array_clear(texture_cache);
78}
79
80bool LoadMap(const char *filename) {
81 TraceLog(LOG_INFO, "Loading map: %s", filename);
82
83 Map map = ParseMap(filename);
84 if (map.entity_count == 0) {
85 TraceLog(LOG_ERROR, "Failed to load map or map is empty: %s", filename);
86 return false;
87 }
88
89 UnloadMap();
90
91 array(TextureGroup) groups;
92 array_init(groups);
93
94 // Default camera if no start found
95 game.camera.position = (Vector3){ 0, 10, 0 };
96 game.camera.target = (Vector3){ 1, 10, 0 };
97 game.camera.up = (Vector3){ 0, 1, 0 };
98 game.camera.fovy = 75.0f;
99 game.camera.projection = CAMERA_PERSPECTIVE;
100
101 int total_brushes = 0;
102 for (int i = 0; i < map.entity_count; i++) {
103 MapEntity *e = &map.entities[i];
104 bool is_world = false;
105 const char *classname = "";
106 for (int j = 0; j < e->property_count; j++) {
107 if (strcmp(e->properties[j].key, "classname") == 0) {
108 classname = e->properties[j].value;
109 if (strcmp(classname, "worldspawn") == 0) is_world = true;
110 }
111 if (strcmp(e->properties[j].key, "origin") == 0) {
112 float x, y, z;
113 sscanf(e->properties[j].value, "%f %f %f", &x, &y, &z);
114 if (strcmp(classname, "info_player_start") == 0) {
115 game.camera.position = (Vector3){ x, z, -y };
116 game.camera.target = Vector3Add(game.camera.position, (Vector3){0, 0, 1});
117 TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f", game.camera.position.x, game.camera.position.y, game.camera.position.z);
118 }
119 }
120 }
121
122 if (is_world) {
123 total_brushes += e->brush_count;
124 for (int j = 0; j < e->brush_count; j++) {
125 MapBrush *brush = &e->brushes[j];
126 for (int p_idx = 0; p_idx < brush->plane_count; p_idx++) {
127 MapPlane *mp = &brush->planes[p_idx];
128 Plane plane = PlaneFromPoints(mp->p[0], mp->p[1], mp->p[2]);
129
130 if (Vector3Length(plane.normal) < 0.5f) continue;
131
132 Polygon poly = CreateLargeQuad(plane);
133 for (int k = 0; k < brush->plane_count; k++) {
134 if (p_idx == k) continue;
135 Plane clipPlane = PlaneFromPoints(brush->planes[k].p[0], brush->planes[k].p[1], brush->planes[k].p[2]);
136 if (Vector3Length(clipPlane.normal) < 0.5f) continue;
137
138 Polygon next = PolyClip(poly, clipPlane);
139 PolyFree(poly);
140 poly = next;
141 }
142 if (poly.count >= 3) {
143 TextureGroup *group = NULL;
144 for (int g = 0; g < groups.length; g++) {
145 if (strcmp(groups.data[g].texture, mp->texture) == 0) {
146 group = &groups.data[g];
147 break;
148 }
149 }
150 if (!group) {
151 TextureGroup new_group = { 0 };
152 strncpy(new_group.texture, mp->texture, sizeof(new_group.texture));
153 array_init(new_group.vertices);
154 array_init(new_group.texcoords);
155 array_init(new_group.normals);
156 array_push(groups, new_group);
157 group = &groups.data[groups.length - 1];
158 }
159 for (int v = 1; v < poly.count - 1; v++) {
160 Vector3 v0 = poly.verts[0];
161 Vector3 v1 = poly.verts[v+1];
162 Vector3 v2 = poly.verts[v];
163 array_push(group->vertices, v0.x); array_push(group->vertices, v0.y); array_push(group->vertices, v0.z);
164 array_push(group->vertices, v1.x); array_push(group->vertices, v1.y); array_push(group->vertices, v1.z);
165 array_push(group->vertices, v2.x); array_push(group->vertices, v2.y); array_push(group->vertices, v2.z);
166 Vector2 uv0 = GetUV(v0, mp, plane);
167 Vector2 uv1 = GetUV(v1, mp, plane);
168 Vector2 uv2 = GetUV(v2, mp, plane);
169 array_push(group->texcoords, uv0.x); array_push(group->texcoords, uv0.y);
170 array_push(group->texcoords, uv1.x); array_push(group->texcoords, uv1.y);
171 array_push(group->texcoords, uv2.x); array_push(group->texcoords, uv2.y);
172 Vector3 out_normal = Vector3Scale(plane.normal, -1.0f);
173 array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
174 array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
175 array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
176 }
177 }
178 PolyFree(poly);
179 }
180 }
181 }
182 }
183
184 array(Model) final_models;
185 array_init(final_models);
186
187 for (int i = 0; i < groups.length; i++) {
188 TextureGroup *g = &groups.data[i];
189 if (g->vertices.length == 0) continue;
190 Mesh mesh = { 0 };
191 mesh.vertexCount = (int)g->vertices.length / 3;
192 mesh.triangleCount = mesh.vertexCount / 3;
193 mesh.vertices = (float *)malloc(g->vertices.length * sizeof(float));
194 memcpy(mesh.vertices, g->vertices.data, g->vertices.length * sizeof(float));
195 mesh.texcoords = (float *)malloc(g->texcoords.length * sizeof(float));
196 memcpy(mesh.texcoords, g->texcoords.data, g->texcoords.length * sizeof(float));
197 mesh.normals = (float *)malloc(g->normals.length * sizeof(float));
198 memcpy(mesh.normals, g->normals.data, g->normals.length * sizeof(float));
199 UploadMesh(&mesh, false);
200 Model model = LoadModelFromMesh(mesh);
201 model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = GetTexture(g->texture);
202 array_push(final_models, model);
203 array_free(g->vertices);
204 array_free(g->texcoords);
205 array_free(g->normals);
206 }
207
208 game.world_models = final_models.data;
209 game.world_model_count = (int)final_models.length;
210 array_free(groups);
211
212 FreeMap(map);
213 TraceLog(LOG_INFO, "Processed %d brushes into %d models", total_brushes, game.world_model_count);
214 return true;
215}
216
217void InitGame(void) {
218 array_init(texture_cache);
219 game.world_models = NULL;
220 game.world_model_count = 0;
221
222 LoadMap("maps/demo1.map");
223
224 game.cursor_captured = true;
225 DisableCursor();
226}
227
228void UpdateGame(void) {
229 float dt = GetFrameTime();
230
231 if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) {
232 game.cursor_captured = !game.cursor_captured;
233 if (game.cursor_captured) DisableCursor();
234 else EnableCursor();
235 }
236
237 if (IsKeyPressed(KEY_ONE)) LoadMap("maps/demo1.map");
238 if (IsKeyPressed(KEY_TWO)) LoadMap("maps/demo2.map");
239
240 if (IsKeyPressed(KEY_V)) {
241 game.vsync = !game.vsync;
242 if (game.vsync) {
243 SetWindowState(FLAG_VSYNC_HINT);
244 SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor()));
245 } else {
246 ClearWindowState(FLAG_VSYNC_HINT);
247 SetTargetFPS(0);
248 }
249 }
250
251 // Manual first-person movement
252 Vector3 forward = Vector3Normalize(Vector3Subtract(game.camera.target, game.camera.position));
253 Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, game.camera.up));
254
255 Vector3 move = { 0 };
256 if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward);
257 if (IsKeyDown(KEY_S)) move = Vector3Subtract(move, forward);
258 if (IsKeyDown(KEY_D)) move = Vector3Add(move, right);
259 if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right);
260
261 if (Vector3Length(move) > 0) {
262 move = Vector3Scale(Vector3Normalize(move), PLAYER_MOVE_SPEED * dt);
263 game.camera.position = Vector3Add(game.camera.position, move);
264 game.camera.target = Vector3Add(game.camera.target, move);
265 }
266
267 if (game.cursor_captured) {
268 // Manual rotation
269 Vector2 mouseDelta = GetMouseDelta();
270 float yaw = -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY;
271 float pitch = -mouseDelta.y * PLAYER_MOUSE_SENSITIVITY;
272
273 Vector3 view = Vector3Subtract(game.camera.target, game.camera.position);
274
275 // Yaw
276 view = Vector3RotateByAxisAngle(view, game.camera.up, yaw);
277
278 // Pitch
279 Vector3 axis = Vector3Normalize(Vector3CrossProduct(view, game.camera.up));
280 view = Vector3RotateByAxisAngle(view, axis, pitch);
281
282 game.camera.target = Vector3Add(game.camera.position, view);
283 }
284}
285
286void DrawGame(void) {
287 BeginDrawing();
288 ClearBackground(DARKGRAY);
289 BeginMode3D(game.camera);
290
291 // Enable backface culling to hide interior faces of brushes
292 rlEnableBackfaceCulling();
293 for (int i = 0; i < game.world_model_count; i++) {
294 DrawModel(game.world_models[i], (Vector3){ 0, 0, 0 }, 1.0f, WHITE);
295 }
296
297 EndMode3D();
298
299 int screenWidth = GetScreenWidth();
300 int screenHeight = GetScreenHeight();
301 DrawLine(screenWidth / 2 - 10, screenHeight / 2, screenWidth / 2 + 10, screenHeight / 2, GREEN);
302 DrawLine(screenWidth / 2, screenHeight / 2 - 10, screenWidth / 2, screenHeight / 2 + 10, GREEN);
303 DrawFPS(10, 10);
304 DrawText(TextFormat("VSync: %s", game.vsync ? "ON" : "OFF"), 10, 30, 20, GREEN);
305 EndDrawing();
306}
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);
77#define UNUSED(value) (void)(value) 77#define UNUSED(value) (void)(value)
78#define TODO(message) \ 78#define TODO(message) \
79 do { \ 79 do { \
80 fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); \ 80 TraceLog(LOG_ERROR, "%s:%d: TODO: %s", __FILE__, __LINE__, message); \
81 abort(); \ 81 abort(); \
82 } while (0) 82 } while (0)
83#define UNREACHABLE(message) \ 83#define UNREACHABLE(message) \
84 do { \ 84 do { \
85 fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); \ 85 TraceLog(LOG_ERROR, "%s:%d: UNREACHABLE: %s", __FILE__, __LINE__, message); \
86 abort(); \ 86 abort(); \
87 } while (0) 87 } while (0)
88 88
@@ -315,29 +315,6 @@ NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
315NONSTD_DEF int write_file_sv(const char *filepath, stringv sv); 315NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
316NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb); 316NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
317 317
318// Logging
319typedef enum {
320 LOG_ERROR,
321 LOG_WARN,
322 LOG_INFO,
323 LOG_DEBUG,
324} LogLevel;
325
326NONSTD_DEF void set_log_level(LogLevel level);
327NONSTD_DEF LogLevel get_log_level_from_env(void);
328NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...);
329
330#define LOG_INFO_MSG(...) log_message(stdout, LOG_INFO, __VA_ARGS__)
331#define LOG_DEBUG_MSG(...) log_message(stdout, LOG_DEBUG, __VA_ARGS__)
332#define LOG_WARN_MSG(...) log_message(stderr, LOG_WARN, __VA_ARGS__)
333#define LOG_ERROR_MSG(...) log_message(stderr, LOG_ERROR, __VA_ARGS__)
334
335#define COLOR_RESET "\033[0m"
336#define COLOR_INFO "\033[32m"
337#define COLOR_DEBUG "\033[36m"
338#define COLOR_WARNING "\033[33m"
339#define COLOR_ERROR "\033[31m"
340
341#ifdef NONSTD_IMPLEMENTATION 318#ifdef NONSTD_IMPLEMENTATION
342 319
343NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) { 320NONSTD_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) {
587 return write_entire_file(filepath, sb->data, sb->length); 564 return write_entire_file(filepath, sb->data, sb->length);
588} 565}
589 566
590// Logging Implementation
591
592static LogLevel max_level = LOG_INFO;
593
594static const char *level_strings[] = {
595 "ERROR",
596 "WARN",
597 "INFO",
598 "DEBUG",
599};
600
601static const char *level_colors[] = {
602 COLOR_ERROR,
603 COLOR_WARNING,
604 COLOR_INFO,
605 COLOR_DEBUG,
606};
607
608NONSTD_DEF void set_log_level(LogLevel level) {
609 max_level = level;
610}
611
612NONSTD_DEF LogLevel get_log_level_from_env(void) {
613 const char *env = getenv("LOG_LEVEL");
614 if (env) {
615 int level = atoi(env);
616 if (level >= 0 && level <= 3) {
617 return (LogLevel)level;
618 }
619 }
620
621 return max_level;
622}
623
624NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...) {
625 if (max_level < level)
626 return;
627
628 struct timeval tv;
629 gettimeofday(&tv, NULL);
630 struct tm *tm_info = localtime(&tv.tv_sec);
631
632 char time_str[24];
633 strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
634
635 const char *color = isatty(fileno(stream)) ? level_colors[level] : "";
636 const char *reset = isatty(fileno(stream)) ? COLOR_RESET : "";
637
638 const char *log_format = "%s[%s.%03d] [%-5s] ";
639 fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000), level_strings[level]);
640
641 va_list args;
642 va_start(args, format);
643 vfprintf(stream, format, args);
644 va_end(args);
645
646 fprintf(stream, "%s\n", reset);
647 fflush(stream);
648}
649
650// PPM Image Implementation 567// PPM Image Implementation
651 568
652NONSTD_DEF Canvas ppm_init(u32 width, u32 height) { 569NONSTD_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) {
57 const char* debug_env = getenv("DEBUG"); 57 const char* debug_env = getenv("DEBUG");
58 if (debug_env && (strcmp(debug_env, "1") == 0 || strcmp(debug_env, "true") == 0)) { 58 if (debug_env && (strcmp(debug_env, "1") == 0 || strcmp(debug_env, "true") == 0)) {
59 g_disk_mode = 1; 59 g_disk_mode = 1;
60 log_message(stdout, LOG_INFO, "VFS: Operating in Disk Mode (DEBUG enabled)"); 60 TraceLog(LOG_INFO, "VFS: Operating in Disk Mode (DEBUG enabled)");
61 return; 61 return;
62 } 62 }
63 63
64 FILE* f = fopen(pak_path, "rb"); 64 FILE* f = fopen(pak_path, "rb");
65 if (!f) { 65 if (!f) {
66 log_message(stderr, LOG_ERROR, "VFS: Failed to open pak file: %s (Error: %s)", pak_path, strerror(errno)); 66 TraceLog(LOG_ERROR, "VFS: Failed to open pak file: %s (Error: %s)", pak_path, strerror(errno));
67 log_message(stderr, LOG_WARN, "VFS: Falling back to Disk Mode"); 67 TraceLog(LOG_WARNING, "VFS: Falling back to Disk Mode");
68 g_disk_mode = 1; 68 g_disk_mode = 1;
69 return; 69 return;
70 } 70 }
71 71
72 VfsHeader header; 72 VfsHeader header;
73 if (fread(&header, sizeof(VfsHeader), 1, f) != 1) { 73 if (fread(&header, sizeof(VfsHeader), 1, f) != 1) {
74 log_message(stderr, LOG_ERROR, "VFS: Failed to read header from %s", pak_path); 74 TraceLog(LOG_ERROR, "VFS: Failed to read header from %s", pak_path);
75 fclose(f); 75 fclose(f);
76 g_disk_mode = 1; 76 g_disk_mode = 1;
77 return; 77 return;
78 } 78 }
79 79
80 if (memcmp(header.magic, "DRP1", 4) != 0) { 80 if (memcmp(header.magic, "DRP1", 4) != 0) {
81 log_message(stderr, LOG_ERROR, "VFS: Invalid magic in %s", pak_path); 81 TraceLog(LOG_ERROR, "VFS: Invalid magic in %s", pak_path);
82 fclose(f); 82 fclose(f);
83 g_disk_mode = 1; 83 g_disk_mode = 1;
84 return; 84 return;
@@ -87,7 +87,7 @@ void vfs_init(const char* pak_path) {
87 g_num_entries = header.num_files; 87 g_num_entries = header.num_files;
88 g_entries = (VfsEntry*)malloc(sizeof(VfsEntry) * g_num_entries); 88 g_entries = (VfsEntry*)malloc(sizeof(VfsEntry) * g_num_entries);
89 if (fread(g_entries, sizeof(VfsEntry), g_num_entries, f) != g_num_entries) { 89 if (fread(g_entries, sizeof(VfsEntry), g_num_entries, f) != g_num_entries) {
90 log_message(stderr, LOG_ERROR, "VFS: Failed to read index table from %s", pak_path); 90 TraceLog(LOG_ERROR, "VFS: Failed to read index table from %s", pak_path);
91 free(g_entries); 91 free(g_entries);
92 g_entries = NULL; 92 g_entries = NULL;
93 fclose(f); 93 fclose(f);
@@ -97,7 +97,7 @@ void vfs_init(const char* pak_path) {
97 97
98 fclose(f); 98 fclose(f);
99 strncpy(g_pak_path, pak_path, sizeof(g_pak_path) - 1); 99 strncpy(g_pak_path, pak_path, sizeof(g_pak_path) - 1);
100 log_message(stdout, LOG_INFO, "VFS: Loaded %u files from %s", g_num_entries, pak_path); 100 TraceLog(LOG_INFO, "VFS: Loaded %u files from %s", g_num_entries, pak_path);
101} 101}
102 102
103void vfs_shutdown(void) { 103void vfs_shutdown(void) {
@@ -137,7 +137,7 @@ VfsFile* vfs_open(const char* path) {
137 } 137 }
138 } 138 }
139 139
140 log_message(stderr, LOG_WARN, "VFS: File not found: %s", path); 140 TraceLog(LOG_WARNING, "VFS: File not found: %s", path);
141 return NULL; 141 return NULL;
142} 142}
143 143
diff --git a/main.c b/main.c
index 091c0f4..bcacb47 100644
--- a/main.c
+++ b/main.c
@@ -1,18 +1,24 @@
1#include "raylib.h" 1#define _POSIX_C_SOURCE 200809L
2#define NONSTD_IMPLEMENTATION
3#define VFS_IMPLEMENTATION
4#include "all.h"
2 5
3int main(void) 6int main(void) {
4{ 7 SetConfigFlags(FLAG_VSYNC_HINT);
5 InitWindow(800, 450, "raylib example - basic window"); 8 InitWindow(1920, 1080, "Stalag");
9 SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor()));
6 10
7 while (!WindowShouldClose()) 11 vfs_init("data.pak");
8 { 12 InitGame();
9 BeginDrawing(); 13 game.vsync = true;
10 ClearBackground(RAYWHITE); 14
11 DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY); 15 while (!WindowShouldClose()) {
12 EndDrawing(); 16 UpdateGame();
17 DrawGame();
13 } 18 }
14 19
20 vfs_shutdown();
15 CloseWindow(); 21 CloseWindow();
16 22
17 return 0; 23 return 0;
18} \ No newline at end of file 24}
diff --git a/map.c b/map.c
new file mode 100644
index 0000000..f28476f
--- /dev/null
+++ b/map.c
@@ -0,0 +1,246 @@
1#include "all.h"
2#include <ctype.h>
3#include <stdlib.h>
4#include <stdio.h>
5
6char map_peek(MapParser *p) {
7 if (p->pos >= p->length) return 0;
8 return p->data[p->pos];
9}
10
11char map_get(MapParser *p) {
12 if (p->pos >= p->length) return 0;
13 return p->data[p->pos++];
14}
15
16void map_skip_whitespace(MapParser *p) {
17 while (true) {
18 char c = map_peek(p);
19 if (isspace(c)) {
20 map_get(p);
21 } else if (c == '/' && p->data[p->pos + 1] == '/') {
22 while (map_peek(p) != '\n' && map_peek(p) != 0) map_get(p);
23 } else {
24 break;
25 }
26 }
27}
28
29bool map_expect(MapParser *p, char expected) {
30 map_skip_whitespace(p);
31 if (map_get(p) == expected) return true;
32 return false;
33}
34
35void map_parse_token(MapParser *p, char *buffer, int size) {
36 map_skip_whitespace(p);
37 int i = 0;
38 bool quoted = false;
39 if (map_peek(p) == '"') {
40 quoted = true;
41 map_get(p);
42 }
43
44 while (true) {
45 char c = map_peek(p);
46 if (c == 0) break;
47 if (quoted) {
48 if (c == '"') {
49 map_get(p);
50 break;
51 }
52 } else {
53 if (isspace(c) || c == '(' || c == ')' || c == '{' || c == '}') break;
54 }
55 if (i < size - 1) buffer[i++] = map_get(p);
56 else map_get(p);
57 }
58 buffer[i] = 0;
59}
60
61Vector3 map_parse_vector(MapParser *p) {
62 map_expect(p, '(');
63 char buf[64];
64 map_parse_token(p, buf, sizeof(buf));
65 float x = (float)atof(buf);
66 map_parse_token(p, buf, sizeof(buf));
67 float y = (float)atof(buf);
68 map_parse_token(p, buf, sizeof(buf));
69 float z = (float)atof(buf);
70 map_expect(p, ')');
71 return (Vector3){ x, z, -y };
72}
73
74Map ParseMap(const char *filename) {
75 size_t size;
76 char *data = read_entire_file(filename, &size);
77 Map map = { 0 };
78 if (!data) return map;
79
80 MapParser p = { data, size, 0 };
81 array(MapEntity) entities;
82 array_init(entities);
83
84 while (true) {
85 map_skip_whitespace(&p);
86 if (map_peek(&p) == 0) break;
87
88 if (map_expect(&p, '{')) {
89 MapEntity entity = { 0 };
90 array(MapProperty) props;
91 array(MapBrush) brushes;
92 array_init(props);
93 array_init(brushes);
94
95 while (true) {
96 map_skip_whitespace(&p);
97 char c = map_peek(&p);
98 if (c == '}') {
99 map_get(&p);
100 break;
101 } else if (c == '"') {
102 MapProperty prop;
103 map_parse_token(&p, prop.key, sizeof(prop.key));
104 map_parse_token(&p, prop.value, sizeof(prop.value));
105 array_push(props, prop);
106 } else if (c == '{') {
107 map_get(&p);
108 MapBrush brush = { 0 };
109 array(MapPlane) planes;
110 array_init(planes);
111 while (true) {
112 map_skip_whitespace(&p);
113 if (map_peek(&p) == '}') {
114 map_get(&p);
115 break;
116 }
117 MapPlane plane;
118 plane.p[0] = map_parse_vector(&p);
119 plane.p[1] = map_parse_vector(&p);
120 plane.p[2] = map_parse_vector(&p);
121 map_parse_token(&p, plane.texture, sizeof(plane.texture));
122
123 char buf[64];
124 map_parse_token(&p, buf, sizeof(buf)); plane.shift[0] = (float)atof(buf);
125 map_parse_token(&p, buf, sizeof(buf)); plane.shift[1] = (float)atof(buf);
126 map_parse_token(&p, buf, sizeof(buf)); plane.rotate = (float)atof(buf);
127 map_parse_token(&p, buf, sizeof(buf)); plane.scale[0] = (float)atof(buf);
128 map_parse_token(&p, buf, sizeof(buf)); plane.scale[1] = (float)atof(buf);
129
130 array_push(planes, plane);
131 }
132 brush.planes = planes.data;
133 brush.plane_count = (int)planes.length;
134 array_push(brushes, brush);
135 } else {
136 map_get(&p);
137 }
138 }
139 entity.properties = props.data;
140 entity.property_count = (int)props.length;
141 entity.brushes = brushes.data;
142 entity.brush_count = (int)brushes.length;
143 array_push(entities, entity);
144 }
145 }
146
147 map.entities = entities.data;
148 map.entity_count = (int)entities.length;
149 free(data);
150 return map;
151}
152
153void FreeMap(Map map) {
154 for (int i = 0; i < map.entity_count; i++) {
155 MapEntity *e = &map.entities[i];
156 if (e->properties) free(e->properties);
157 for (int j = 0; j < e->brush_count; j++) {
158 if (e->brushes[j].planes) free(e->brushes[j].planes);
159 }
160 if (e->brushes) free(e->brushes);
161 }
162 if (map.entities) free(map.entities);
163}
164
165Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3) {
166 Vector3 v1 = Vector3Subtract(p2, p1);
167 Vector3 v2 = Vector3Subtract(p3, p1);
168 Vector3 normal = Vector3Normalize(Vector3CrossProduct(v1, v2));
169 return (Plane){ normal, Vector3DotProduct(normal, p1) };
170}
171
172void PolyFree(Polygon p) {
173 if (p.verts) free(p.verts);
174}
175
176Polygon PolyClip(Polygon poly, Plane plane) {
177 if (poly.count == 0) return poly;
178
179 array(Vector3) out_verts;
180 array_init(out_verts);
181
182 for (int i = 0; i < poly.count; i++) {
183 Vector3 p1 = poly.verts[i];
184 Vector3 p2 = poly.verts[(i + 1) % poly.count];
185
186 float d1 = Vector3DotProduct(plane.normal, p1) - plane.dist;
187 float d2 = Vector3DotProduct(plane.normal, p2) - plane.dist;
188
189 // Using a smaller epsilon and more robust logic
190 const float eps = 0.001f;
191
192 if (d1 >= -eps) {
193 if (d2 >= -eps) {
194 array_push(out_verts, p2);
195 } else {
196 float t = d1 / (d1 - d2);
197 Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t));
198 array_push(out_verts, intersect);
199 }
200 } else {
201 if (d2 >= -eps) {
202 float t = d1 / (d1 - d2);
203 Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t));
204 array_push(out_verts, intersect);
205 array_push(out_verts, p2);
206 }
207 }
208 }
209
210 Polygon res;
211 res.verts = out_verts.data;
212 res.count = (int)out_verts.length;
213 return res;
214}
215
216Polygon CreateLargeQuad(Plane plane) {
217 Vector3 n = plane.normal;
218 Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 };
219 Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n));
220 up = Vector3CrossProduct(n, right);
221
222 Vector3 center = Vector3Scale(n, plane.dist);
223 float size = 10000.0f;
224
225 Polygon poly;
226 poly.verts = ALLOC(Vector3, 4);
227 poly.count = 4;
228 poly.verts[0] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, size)));
229 poly.verts[1] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, size)));
230 poly.verts[2] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, -size)));
231 poly.verts[3] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, -size)));
232
233 return poly;
234}
235
236Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane) {
237 Vector3 n = plane.normal;
238 Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 };
239 Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n));
240 up = Vector3CrossProduct(n, right);
241
242 float u = Vector3DotProduct(p, right) * (1.0f / (mp->scale[0] ? mp->scale[0] : 1.0f)) + mp->shift[0];
243 float v = Vector3DotProduct(p, up) * (1.0f / (mp->scale[1] ? mp->scale[1] : 1.0f)) + mp->shift[1];
244
245 return (Vector2){ u / 64.0f, v / 64.0f };
246}
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 @@
30( -16 336 16 ) ( -15 336 16 ) ( -16 336 17 ) environment/planks_012 0 0 0 1 1 30( -16 336 16 ) ( -15 336 16 ) ( -16 336 17 ) environment/planks_012 0 0 0 1 1
31( -16 112 16 ) ( -16 112 17 ) ( -16 113 16 ) environment/planks_012 16 0 0 1 1 31( -16 112 16 ) ( -16 112 17 ) ( -16 113 16 ) environment/planks_012 16 0 0 1 1
32} 32}
33// brush 3
34{
35( -16 272 0 ) ( -16 273 0 ) ( -16 272 1 ) environment/planks_012 0 0 0 1 1
36( -16 272 0 ) ( -16 272 1 ) ( -15 272 0 ) environment/planks_012 0 0 0 1 1
37( -16 272 0 ) ( -15 272 0 ) ( -16 273 0 ) environment/planks_012 0 0 0 1 1
38( 80 336 64 ) ( 80 337 64 ) ( 81 336 64 ) environment/planks_012 0 0 0 1 1
39( 80 336 16 ) ( 81 336 16 ) ( 80 336 17 ) environment/planks_012 0 0 0 1 1
40( 80 336 16 ) ( 80 336 17 ) ( 80 337 16 ) environment/planks_012 0 0 0 1 1
41}
42}
43// entity 1
44{
45"classname" "info_player_start"
46"origin" "-112 -16 40"
33} 47}
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