aboutsummaryrefslogtreecommitdiff
path: root/game.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-04-30 19:19:27 +0200
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-04-30 19:44:07 +0200
commitb4d0ad9e95226d225d5361b1182866884aaa6366 (patch)
tree5440b34d97e89a08fc01abd49bb80e5b50dda945 /game.c
parentddde2e8fdf05efac5914ca2b812b7762b78ba9ec (diff)
downloadstalag-b4d0ad9e95226d225d5361b1182866884aaa6366.tar.gz
Structural refactor
Diffstat (limited to 'game.c')
-rw-r--r--game.c359
1 files changed, 48 insertions, 311 deletions
diff --git a/game.c b/game.c
index 67767d3..40d1876 100644
--- a/game.c
+++ b/game.c
@@ -1,332 +1,69 @@
1#include "all.h" 1#include "all.h"
2#include <stdio.h> 2
3#include <string.h> 3#include <string.h>
4#include <ctype.h>
5 4
6GameState game; 5GameState 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 = PLAYER_FOV;
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.pos = (Vector3){ x, z, -y };
116 game.camera.position = game.pos;
117 float angle = 0;
118 for (int k = 0; k < e->property_count; k++) {
119 if (strcmp(e->properties[k].key, "angle") == 0) {
120 angle = (float)atof(e->properties[k].value);
121 }
122 }
123 game.yaw = (angle + 90.0f) * DEG2RAD;
124 game.pitch = 0;
125 game.camera.target = Vector3Add(game.pos, (Vector3){sinf(game.yaw), 0, cosf(game.yaw)});
126 TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f (yaw: %f)", game.pos.x, game.pos.y, game.pos.z, game.yaw);
127 }
128 }
129 }
130
131 if (is_world) {
132 total_brushes += e->brush_count;
133 for (int j = 0; j < e->brush_count; j++) {
134 MapBrush *brush = &e->brushes[j];
135 for (int p_idx = 0; p_idx < brush->plane_count; p_idx++) {
136 MapPlane *mp = &brush->planes[p_idx];
137 Plane plane = PlaneFromPoints(mp->p[0], mp->p[1], mp->p[2]);
138
139 if (Vector3Length(plane.normal) < 0.5f) continue;
140
141 Polygon poly = CreateLargeQuad(plane);
142 for (int k = 0; k < brush->plane_count; k++) {
143 if (p_idx == k) continue;
144 Plane clipPlane = PlaneFromPoints(brush->planes[k].p[0], brush->planes[k].p[1], brush->planes[k].p[2]);
145 if (Vector3Length(clipPlane.normal) < 0.5f) continue;
146
147 Polygon next = PolyClip(poly, clipPlane);
148 PolyFree(poly);
149 poly = next;
150 }
151 if (poly.count >= 3) {
152 TextureGroup *group = NULL;
153 for (int g = 0; g < groups.length; g++) {
154 if (strcmp(groups.data[g].texture, mp->texture) == 0) {
155 group = &groups.data[g];
156 break;
157 }
158 }
159 if (!group) {
160 TextureGroup new_group = { 0 };
161 strncpy(new_group.texture, mp->texture, sizeof(new_group.texture));
162 array_init(new_group.vertices);
163 array_init(new_group.texcoords);
164 array_init(new_group.normals);
165 array_push(groups, new_group);
166 group = &groups.data[groups.length - 1];
167 }
168 for (int v = 1; v < poly.count - 1; v++) {
169 Vector3 v0 = poly.verts[0];
170 Vector3 v1 = poly.verts[v+1];
171 Vector3 v2 = poly.verts[v];
172 array_push(group->vertices, v0.x); array_push(group->vertices, v0.y); array_push(group->vertices, v0.z);
173 array_push(group->vertices, v1.x); array_push(group->vertices, v1.y); array_push(group->vertices, v1.z);
174 array_push(group->vertices, v2.x); array_push(group->vertices, v2.y); array_push(group->vertices, v2.z);
175 Vector2 uv0 = GetUV(v0, mp, plane);
176 Vector2 uv1 = GetUV(v1, mp, plane);
177 Vector2 uv2 = GetUV(v2, mp, plane);
178 array_push(group->texcoords, uv0.x); array_push(group->texcoords, uv0.y);
179 array_push(group->texcoords, uv1.x); array_push(group->texcoords, uv1.y);
180 array_push(group->texcoords, uv2.x); array_push(group->texcoords, uv2.y);
181 Vector3 out_normal = Vector3Scale(plane.normal, -1.0f);
182 array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
183 array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
184 array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
185 }
186 }
187 PolyFree(poly);
188 }
189 }
190 }
191 }
192
193 array(Model) final_models;
194 array_init(final_models);
195
196 for (int i = 0; i < groups.length; i++) {
197 TextureGroup *g = &groups.data[i];
198 if (g->vertices.length == 0) continue;
199 Mesh mesh = { 0 };
200 mesh.vertexCount = (int)g->vertices.length / 3;
201 mesh.triangleCount = mesh.vertexCount / 3;
202 mesh.vertices = (float *)malloc(g->vertices.length * sizeof(float));
203 memcpy(mesh.vertices, g->vertices.data, g->vertices.length * sizeof(float));
204 mesh.texcoords = (float *)malloc(g->texcoords.length * sizeof(float));
205 memcpy(mesh.texcoords, g->texcoords.data, g->texcoords.length * sizeof(float));
206 mesh.normals = (float *)malloc(g->normals.length * sizeof(float));
207 memcpy(mesh.normals, g->normals.data, g->normals.length * sizeof(float));
208 UploadMesh(&mesh, false);
209 Model model = LoadModelFromMesh(mesh);
210 model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = GetTexture(g->texture);
211 array_push(final_models, model);
212 array_free(g->vertices);
213 array_free(g->texcoords);
214 array_free(g->normals);
215 }
216
217 game.world_models = final_models.data;
218 game.world_model_count = (int)final_models.length;
219 array_free(groups);
220
221 FreeMap(map);
222 TraceLog(LOG_INFO, "Processed %d brushes into %d models", total_brushes, game.world_model_count);
223 return true;
224}
225 6
226void InitGame(void) { 7void InitGame(void) {
227 array_init(texture_cache); 8 memset(&game, 0, sizeof(game));
228 game.world_models = NULL; 9
229 game.world_model_count = 0; 10 game.font_ui = LoadFontVFS(UI_FONT_PATH, UI_FONT_SIZE);
230 11 game.mode = STATE_MENU;
231 // Load UI Font 12 game.cursor_captured = false;
232 size_t font_size = 0; 13 EnableCursor();
233 void *font_data = vfs_read("fonts/LiberationSans-Bold.ttf", &font_size); 14
234 if (font_data) { 15 game.player.move_mode = MOVE_NORMAL;
235 game.font_ui = LoadFontFromMemory(".ttf", font_data, (int)font_size, 20, NULL, 0); 16 game.player.pos = (Vector3){ 0, 0, 0 };
236 vfs_free(font_data); 17 game.player.velocity = (Vector3){ 0, 0, 0 };
237 } else { 18 game.player.is_grounded = false;
238 game.font_ui = GetFontDefault(); 19
239 } 20 // Camera setup
240 21 game.camera.up = (Vector3){ 0, 1, 0 };
241 game.mode = STATE_TITLE; 22 game.camera.fovy = PLAYER_FOV;
242 23 game.camera.projection = CAMERA_PERSPECTIVE;
243 game.cursor_captured = false;
244 EnableCursor();
245
246 game.move_mode = MOVE_NORMAL;
247 game.velocity = (Vector3){ 0, 0, 0 };
248 game.is_grounded = false;
249} 24}
250 25
251void SetMap(const char *path) { 26void SetMap(const char *path) {
252 strncpy(game.map_path, path, sizeof(game.map_path) - 1); 27 strncpy(game.map_path, path, sizeof(game.map_path) - 1);
253 game.map_path[sizeof(game.map_path) - 1] = '\0'; 28 game.map_path[sizeof(game.map_path) - 1] = '\0';
254} 29}
255 30
256void UpdateGame(void) { 31void UpdateGame(void) {
257 if (game.mode == STATE_TITLE) { 32 if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) {
258 if (IsKeyPressed(KEY_ENTER)) { 33 game.cursor_captured = !game.cursor_captured;
259 if (LoadMap(game.map_path)) { 34 if (game.cursor_captured) DisableCursor();
260 game.mode = STATE_PLAYING; 35 else EnableCursor();
261 } 36 }
262 } 37
263 return; 38 if (IsKeyPressed(KEY_V)) {
264 } 39 game.vsync = !game.vsync;
265 40 if (game.vsync) {
266 if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { 41 SetWindowState(FLAG_VSYNC_HINT);
267 game.cursor_captured = !game.cursor_captured; 42 SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor()));
268 if (game.cursor_captured) DisableCursor(); 43 } else {
269 else EnableCursor(); 44 ClearWindowState(FLAG_VSYNC_HINT);
270 } 45 SetTargetFPS(0);
271 46 }
272 if (IsKeyPressed(KEY_ONE)) LoadMap("maps/demo1.map"); 47 }
273 if (IsKeyPressed(KEY_TWO)) LoadMap("maps/demo2.map"); 48
274 if (IsKeyPressed(KEY_THREE)) LoadMap("maps/demo3.map"); 49 UpdatePlayer();
275
276 if (IsKeyPressed(KEY_V)) {
277 game.vsync = !game.vsync;
278 if (game.vsync) {
279 SetWindowState(FLAG_VSYNC_HINT);
280 SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor()));
281 } else {
282 ClearWindowState(FLAG_VSYNC_HINT);
283 SetTargetFPS(0);
284 }
285 }
286
287 UpdatePlayer();
288} 50}
289 51
290void DrawGame(void) { 52void DrawGame(void) {
291 BeginDrawing(); 53 BeginDrawing();
292 ClearBackground(BLACK); 54 ClearBackground(DARKGRAY);
293 55
294 if (game.mode == STATE_TITLE) { 56 BeginMode3D(game.camera);
295 int screenWidth = GetScreenWidth();
296 int screenHeight = GetScreenHeight();
297 57
298 const char *title = "STALAG"; 58 rlEnableBackfaceCulling();
299 const char *sub = "Press ENTER to Start"; 59 for (int i = 0; i < game.map.count; i++) {
300 60 DrawModel(game.map.models[i], (Vector3){ 0, 0, 0 }, 1.0f, WHITE);
301 int titleSize = 60; 61 }
302 int subSize = 20;
303 62
304 Vector2 titlePos = { 63 EndMode3D();
305 (float)(screenWidth - MeasureTextEx(game.font_ui, title, (float)titleSize, 4).x) / 2,
306 (float)screenHeight / 2 - 40
307 };
308 Vector2 subPos = {
309 (float)(screenWidth - MeasureTextEx(game.font_ui, sub, (float)subSize, 2).x) / 2,
310 (float)screenHeight / 2 + 40
311 };
312 64
313 DrawTextEx(game.font_ui, title, titlePos, (float)titleSize, 4, WHITE); 65 DrawCrosshair();
314 DrawTextEx(game.font_ui, sub, subPos, (float)subSize, 2, GRAY); 66 DrawDebugInfo();
315 } else {
316 ClearBackground(DARKGRAY);
317 BeginMode3D(game.camera);
318
319 // Enable backface culling to hide interior faces of brushes
320 rlEnableBackfaceCulling();
321 for (int i = 0; i < game.world_model_count; i++) {
322 DrawModel(game.world_models[i], (Vector3){ 0, 0, 0 }, 1.0f, WHITE);
323 }
324
325 EndMode3D();
326
327 DrawCrosshair();
328 DrawDebugInfo();
329 }
330 67
331 EndDrawing(); 68 EndDrawing();
332} 69}