diff options
Diffstat (limited to 'game.c')
| -rw-r--r-- | game.c | 306 |
1 files changed, 306 insertions, 0 deletions
@@ -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(); +} |
