aboutsummaryrefslogtreecommitdiff
path: root/game.c
diff options
context:
space:
mode:
Diffstat (limited to 'game.c')
-rw-r--r--game.c333
1 files changed, 52 insertions, 281 deletions
diff --git a/game.c b/game.c
index 168cae4..40d1876 100644
--- a/game.c
+++ b/game.c
@@ -1,298 +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 6
43 if (tex.id == 0) { 7void InitGame(void) {
44 TraceLog(LOG_WARNING, "Failed to load texture: '%s' (tried path: '%s')", name, path); 8 memset(&game, 0, sizeof(game));
45 Image img = GenImageChecked(64, 64, 8, 8, (Color){128, 128, 128, 255}, (Color){200, 200, 200, 255}); 9
46 tex = LoadTextureFromImage(img); 10 game.font_ui = LoadFontVFS(UI_FONT_PATH, UI_FONT_SIZE);
47 UnloadImage(img); 11 game.mode = STATE_MENU;
48 } 12 game.cursor_captured = false;
49 13 EnableCursor();
50 if (tex.id != 0) { 14
51 GenTextureMipmaps(&tex); 15 game.player.move_mode = MOVE_NORMAL;
52 SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); 16 game.player.pos = (Vector3){ 0, 0, 0 };
53 SetTextureWrap(tex, TEXTURE_WRAP_REPEAT); 17 game.player.velocity = (Vector3){ 0, 0, 0 };
54 18 game.player.is_grounded = false;
55 CachedTexture cached; 19
56 strncpy(cached.name, name, sizeof(cached.name)); 20 // Camera setup
57 cached.tex = tex; 21 game.camera.up = (Vector3){ 0, 1, 0 };
58 array_push(texture_cache, cached); 22 game.camera.fovy = PLAYER_FOV;
59 } 23 game.camera.projection = CAMERA_PERSPECTIVE;
60
61 return tex;
62} 24}
63 25
64void UnloadMap(void) { 26void SetMap(const char *path) {
65 if (game.world_models) { 27 strncpy(game.map_path, path, sizeof(game.map_path) - 1);
66 for (int i = 0; i < game.world_model_count; i++) { 28 game.map_path[sizeof(game.map_path) - 1] = '\0';
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} 29}
79 30
80bool LoadMap(const char *filename) { 31void UpdateGame(void) {
81 TraceLog(LOG_INFO, "Loading map: %s", filename); 32 if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) {
82 33 game.cursor_captured = !game.cursor_captured;
83 Map map = ParseMap(filename); 34 if (game.cursor_captured) DisableCursor();
84 if (map.entity_count == 0) { 35 else EnableCursor();
85 TraceLog(LOG_ERROR, "Failed to load map or map is empty: %s", filename); 36 }
86 return false; 37
87 } 38 if (IsKeyPressed(KEY_V)) {
88 39 game.vsync = !game.vsync;
89 UnloadMap(); 40 if (game.vsync) {
90 41 SetWindowState(FLAG_VSYNC_HINT);
91 array(TextureGroup) groups; 42 SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor()));
92 array_init(groups); 43 } else {
93 44 ClearWindowState(FLAG_VSYNC_HINT);
94 // Default camera if no start found 45 SetTargetFPS(0);
95 game.camera.position = (Vector3){ 0, 10, 0 }; 46 }
96 game.camera.target = (Vector3){ 1, 10, 0 }; 47 }
97 game.camera.up = (Vector3){ 0, 1, 0 }; 48
98 game.camera.fovy = PLAYER_FOV; 49 UpdatePlayer();
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
226void InitGame(void) {
227 array_init(texture_cache);
228 game.world_models = NULL;
229 game.world_model_count = 0;
230
231 // Load UI Font
232 size_t font_size = 0;
233 void *font_data = vfs_read("fonts/LiberationSans-Bold.ttf", &font_size);
234 if (font_data) {
235 game.font_ui = LoadFontFromMemory(".ttf", font_data, (int)font_size, 20, NULL, 0);
236 vfs_free(font_data);
237 } else {
238 game.font_ui = GetFontDefault();
239 }
240
241 LoadMap("maps/demo1.map");
242
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} 50}
250 51
251void UpdateGame(void) { 52void DrawGame(void) {
252 if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { 53 BeginDrawing();
253 game.cursor_captured = !game.cursor_captured; 54 ClearBackground(DARKGRAY);
254 if (game.cursor_captured) DisableCursor();
255 else EnableCursor();
256 }
257 55
258 if (IsKeyPressed(KEY_ONE)) LoadMap("maps/demo1.map"); 56 BeginMode3D(game.camera);
259 if (IsKeyPressed(KEY_TWO)) LoadMap("maps/demo2.map");
260 57
261 if (IsKeyPressed(KEY_V)) { 58 rlEnableBackfaceCulling();
262 game.vsync = !game.vsync; 59 for (int i = 0; i < game.map.count; i++) {
263 if (game.vsync) { 60 DrawModel(game.map.models[i], (Vector3){ 0, 0, 0 }, 1.0f, WHITE);
264 SetWindowState(FLAG_VSYNC_HINT); 61 }
265 SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor()));
266 } else {
267 ClearWindowState(FLAG_VSYNC_HINT);
268 SetTargetFPS(0);
269 }
270 }
271 62
272 UpdatePlayer(); 63 EndMode3D();
273}
274 64
275void DrawGame(void) { 65 DrawCrosshair();
276 BeginDrawing(); 66 DrawDebugInfo();
277 ClearBackground(DARKGRAY);
278 BeginMode3D(game.camera);
279
280 // Enable backface culling to hide interior faces of brushes
281 rlEnableBackfaceCulling();
282 for (int i = 0; i < game.world_model_count; i++) {
283 DrawModel(game.world_models[i], (Vector3){ 0, 0, 0 }, 1.0f, WHITE);
284 }
285
286 EndMode3D();
287
288 int screenWidth = GetScreenWidth();
289 int screenHeight = GetScreenHeight();
290 DrawLine(screenWidth / 2 - 10, screenHeight / 2, screenWidth / 2 + 10, screenHeight / 2, GREEN);
291 DrawLine(screenWidth / 2, screenHeight / 2 - 10, screenWidth / 2, screenHeight / 2 + 10, GREEN);
292
293 DrawTextEx(game.font_ui, TextFormat("%i FPS", GetFPS()), (Vector2){ 10, 10 }, 20, 2, GREEN);
294 DrawTextEx(game.font_ui, TextFormat("VSync: %s", game.vsync ? "ON" : "OFF"), (Vector2){ 10, 35 }, 20, 2, GREEN);
295 DrawTextEx(game.font_ui, TextFormat("Speed: %.0f", game.horizontal_speed), (Vector2){ 10, 60 }, 20, 2, GREEN);
296 67
297 EndDrawing(); 68 EndDrawing();
298} 69}