summaryrefslogtreecommitdiff
path: root/game.c
diff options
context:
space:
mode:
Diffstat (limited to 'game.c')
-rw-r--r--game.c306
1 files changed, 306 insertions, 0 deletions
diff --git a/game.c b/game.c
new file mode 100644
index 0000000..db04ff7
--- /dev/null
+++ b/game.c
@@ -0,0 +1,306 @@
+#include "all.h"
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+GameState game;
+static array(CachedTexture) texture_cache;
+
+Texture2D GetTexture(const char *name) {
+ for (int i = 0; i < texture_cache.length; i++) {
+ if (strcmp(texture_cache.data[i].name, name) == 0) return texture_cache.data[i].tex;
+ }
+
+ char lname[256];
+ strncpy(lname, name, sizeof(lname));
+ for (int i = 0; lname[i]; i++) lname[i] = tolower(lname[i]);
+
+ char path[256];
+ void *data = NULL;
+ size_t size = 0;
+ const char *ext = ".jpg";
+
+ snprintf(path, sizeof(path), "textures/%s.png", lname);
+ data = vfs_read(path, &size);
+ if (data) {
+ ext = ".png";
+ } else {
+ snprintf(path, sizeof(path), "textures/%s.jpg", lname);
+ data = vfs_read(path, &size);
+ ext = ".jpg";
+ }
+
+ Texture2D tex = { 0 };
+ if (data) {
+ Image img = LoadImageFromMemory(ext, data, (int)size);
+ if (img.data) {
+ tex = LoadTextureFromImage(img);
+ UnloadImage(img);
+ }
+ vfs_free(data);
+ }
+
+ if (tex.id == 0) {
+ TraceLog(LOG_WARNING, "Failed to load texture: '%s' (tried path: '%s')", name, path);
+ Image img = GenImageChecked(64, 64, 8, 8, (Color){128, 128, 128, 255}, (Color){200, 200, 200, 255});
+ tex = LoadTextureFromImage(img);
+ UnloadImage(img);
+ }
+
+ if (tex.id != 0) {
+ GenTextureMipmaps(&tex);
+ SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR);
+ SetTextureWrap(tex, TEXTURE_WRAP_REPEAT);
+
+ CachedTexture cached;
+ strncpy(cached.name, name, sizeof(cached.name));
+ cached.tex = tex;
+ array_push(texture_cache, cached);
+ }
+
+ return tex;
+}
+
+void UnloadMap(void) {
+ if (game.world_models) {
+ for (int i = 0; i < game.world_model_count; i++) {
+ UnloadModel(game.world_models[i]);
+ }
+ free(game.world_models);
+ game.world_models = NULL;
+ game.world_model_count = 0;
+ }
+
+ for (int i = 0; i < texture_cache.length; i++) {
+ UnloadTexture(texture_cache.data[i].tex);
+ }
+ array_clear(texture_cache);
+}
+
+bool LoadMap(const char *filename) {
+ TraceLog(LOG_INFO, "Loading map: %s", filename);
+
+ Map map = ParseMap(filename);
+ if (map.entity_count == 0) {
+ TraceLog(LOG_ERROR, "Failed to load map or map is empty: %s", filename);
+ return false;
+ }
+
+ UnloadMap();
+
+ array(TextureGroup) groups;
+ array_init(groups);
+
+ // Default camera if no start found
+ game.camera.position = (Vector3){ 0, 10, 0 };
+ game.camera.target = (Vector3){ 1, 10, 0 };
+ game.camera.up = (Vector3){ 0, 1, 0 };
+ game.camera.fovy = 75.0f;
+ game.camera.projection = CAMERA_PERSPECTIVE;
+
+ int total_brushes = 0;
+ for (int i = 0; i < map.entity_count; i++) {
+ MapEntity *e = &map.entities[i];
+ bool is_world = false;
+ const char *classname = "";
+ for (int j = 0; j < e->property_count; j++) {
+ if (strcmp(e->properties[j].key, "classname") == 0) {
+ classname = e->properties[j].value;
+ if (strcmp(classname, "worldspawn") == 0) is_world = true;
+ }
+ if (strcmp(e->properties[j].key, "origin") == 0) {
+ float x, y, z;
+ sscanf(e->properties[j].value, "%f %f %f", &x, &y, &z);
+ if (strcmp(classname, "info_player_start") == 0) {
+ game.camera.position = (Vector3){ x, z, -y };
+ game.camera.target = Vector3Add(game.camera.position, (Vector3){0, 0, 1});
+ TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f", game.camera.position.x, game.camera.position.y, game.camera.position.z);
+ }
+ }
+ }
+
+ if (is_world) {
+ total_brushes += e->brush_count;
+ for (int j = 0; j < e->brush_count; j++) {
+ MapBrush *brush = &e->brushes[j];
+ for (int p_idx = 0; p_idx < brush->plane_count; p_idx++) {
+ MapPlane *mp = &brush->planes[p_idx];
+ Plane plane = PlaneFromPoints(mp->p[0], mp->p[1], mp->p[2]);
+
+ if (Vector3Length(plane.normal) < 0.5f) continue;
+
+ Polygon poly = CreateLargeQuad(plane);
+ for (int k = 0; k < brush->plane_count; k++) {
+ if (p_idx == k) continue;
+ Plane clipPlane = PlaneFromPoints(brush->planes[k].p[0], brush->planes[k].p[1], brush->planes[k].p[2]);
+ if (Vector3Length(clipPlane.normal) < 0.5f) continue;
+
+ Polygon next = PolyClip(poly, clipPlane);
+ PolyFree(poly);
+ poly = next;
+ }
+ if (poly.count >= 3) {
+ TextureGroup *group = NULL;
+ for (int g = 0; g < groups.length; g++) {
+ if (strcmp(groups.data[g].texture, mp->texture) == 0) {
+ group = &groups.data[g];
+ break;
+ }
+ }
+ if (!group) {
+ TextureGroup new_group = { 0 };
+ strncpy(new_group.texture, mp->texture, sizeof(new_group.texture));
+ array_init(new_group.vertices);
+ array_init(new_group.texcoords);
+ array_init(new_group.normals);
+ array_push(groups, new_group);
+ group = &groups.data[groups.length - 1];
+ }
+ for (int v = 1; v < poly.count - 1; v++) {
+ Vector3 v0 = poly.verts[0];
+ Vector3 v1 = poly.verts[v+1];
+ Vector3 v2 = poly.verts[v];
+ array_push(group->vertices, v0.x); array_push(group->vertices, v0.y); array_push(group->vertices, v0.z);
+ array_push(group->vertices, v1.x); array_push(group->vertices, v1.y); array_push(group->vertices, v1.z);
+ array_push(group->vertices, v2.x); array_push(group->vertices, v2.y); array_push(group->vertices, v2.z);
+ Vector2 uv0 = GetUV(v0, mp, plane);
+ Vector2 uv1 = GetUV(v1, mp, plane);
+ Vector2 uv2 = GetUV(v2, mp, plane);
+ array_push(group->texcoords, uv0.x); array_push(group->texcoords, uv0.y);
+ array_push(group->texcoords, uv1.x); array_push(group->texcoords, uv1.y);
+ array_push(group->texcoords, uv2.x); array_push(group->texcoords, uv2.y);
+ Vector3 out_normal = Vector3Scale(plane.normal, -1.0f);
+ array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
+ array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
+ array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
+ }
+ }
+ PolyFree(poly);
+ }
+ }
+ }
+ }
+
+ array(Model) final_models;
+ array_init(final_models);
+
+ for (int i = 0; i < groups.length; i++) {
+ TextureGroup *g = &groups.data[i];
+ if (g->vertices.length == 0) continue;
+ Mesh mesh = { 0 };
+ mesh.vertexCount = (int)g->vertices.length / 3;
+ mesh.triangleCount = mesh.vertexCount / 3;
+ mesh.vertices = (float *)malloc(g->vertices.length * sizeof(float));
+ memcpy(mesh.vertices, g->vertices.data, g->vertices.length * sizeof(float));
+ mesh.texcoords = (float *)malloc(g->texcoords.length * sizeof(float));
+ memcpy(mesh.texcoords, g->texcoords.data, g->texcoords.length * sizeof(float));
+ mesh.normals = (float *)malloc(g->normals.length * sizeof(float));
+ memcpy(mesh.normals, g->normals.data, g->normals.length * sizeof(float));
+ UploadMesh(&mesh, false);
+ Model model = LoadModelFromMesh(mesh);
+ model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = GetTexture(g->texture);
+ array_push(final_models, model);
+ array_free(g->vertices);
+ array_free(g->texcoords);
+ array_free(g->normals);
+ }
+
+ game.world_models = final_models.data;
+ game.world_model_count = (int)final_models.length;
+ array_free(groups);
+
+ FreeMap(map);
+ TraceLog(LOG_INFO, "Processed %d brushes into %d models", total_brushes, game.world_model_count);
+ return true;
+}
+
+void InitGame(void) {
+ array_init(texture_cache);
+ game.world_models = NULL;
+ game.world_model_count = 0;
+
+ LoadMap("maps/demo1.map");
+
+ game.cursor_captured = true;
+ DisableCursor();
+}
+
+void UpdateGame(void) {
+ float dt = GetFrameTime();
+
+ if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) {
+ game.cursor_captured = !game.cursor_captured;
+ if (game.cursor_captured) DisableCursor();
+ else EnableCursor();
+ }
+
+ if (IsKeyPressed(KEY_ONE)) LoadMap("maps/demo1.map");
+ if (IsKeyPressed(KEY_TWO)) LoadMap("maps/demo2.map");
+
+ if (IsKeyPressed(KEY_V)) {
+ game.vsync = !game.vsync;
+ if (game.vsync) {
+ SetWindowState(FLAG_VSYNC_HINT);
+ SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor()));
+ } else {
+ ClearWindowState(FLAG_VSYNC_HINT);
+ SetTargetFPS(0);
+ }
+ }
+
+ // Manual first-person movement
+ Vector3 forward = Vector3Normalize(Vector3Subtract(game.camera.target, game.camera.position));
+ Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, game.camera.up));
+
+ Vector3 move = { 0 };
+ if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward);
+ if (IsKeyDown(KEY_S)) move = Vector3Subtract(move, forward);
+ if (IsKeyDown(KEY_D)) move = Vector3Add(move, right);
+ if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right);
+
+ if (Vector3Length(move) > 0) {
+ move = Vector3Scale(Vector3Normalize(move), PLAYER_MOVE_SPEED * dt);
+ game.camera.position = Vector3Add(game.camera.position, move);
+ game.camera.target = Vector3Add(game.camera.target, move);
+ }
+
+ if (game.cursor_captured) {
+ // Manual rotation
+ Vector2 mouseDelta = GetMouseDelta();
+ float yaw = -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY;
+ float pitch = -mouseDelta.y * PLAYER_MOUSE_SENSITIVITY;
+
+ Vector3 view = Vector3Subtract(game.camera.target, game.camera.position);
+
+ // Yaw
+ view = Vector3RotateByAxisAngle(view, game.camera.up, yaw);
+
+ // Pitch
+ Vector3 axis = Vector3Normalize(Vector3CrossProduct(view, game.camera.up));
+ view = Vector3RotateByAxisAngle(view, axis, pitch);
+
+ game.camera.target = Vector3Add(game.camera.position, view);
+ }
+}
+
+void DrawGame(void) {
+ BeginDrawing();
+ ClearBackground(DARKGRAY);
+ BeginMode3D(game.camera);
+
+ // Enable backface culling to hide interior faces of brushes
+ rlEnableBackfaceCulling();
+ for (int i = 0; i < game.world_model_count; i++) {
+ DrawModel(game.world_models[i], (Vector3){ 0, 0, 0 }, 1.0f, WHITE);
+ }
+
+ EndMode3D();
+
+ int screenWidth = GetScreenWidth();
+ int screenHeight = GetScreenHeight();
+ DrawLine(screenWidth / 2 - 10, screenHeight / 2, screenWidth / 2 + 10, screenHeight / 2, GREEN);
+ DrawLine(screenWidth / 2, screenHeight / 2 - 10, screenWidth / 2, screenHeight / 2 + 10, GREEN);
+ DrawFPS(10, 10);
+ DrawText(TextFormat("VSync: %s", game.vsync ? "ON" : "OFF"), 10, 30, 20, GREEN);
+ EndDrawing();
+}