diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | README.md | 13 | ||||
| -rw-r--r-- | all.h | 161 | ||||
| -rw-r--r-- | assets.c | 81 | ||||
| -rw-r--r-- | config.h | 5 | ||||
| -rw-r--r-- | game.c | 333 | ||||
| -rw-r--r-- | interface.c | 14 | ||||
| -rw-r--r-- | main.c | 106 | ||||
| -rw-r--r-- | map.c | 597 | ||||
| -rw-r--r-- | maps/demo1.map | 4 | ||||
| -rw-r--r-- | maps/demo3.map | 234 | ||||
| -rw-r--r-- | menu.c | 39 | ||||
| -rw-r--r-- | player.c | 184 | ||||
| -rwxr-xr-x | tbrun.sh | 38 | ||||
| -rw-r--r-- | trenchbroom/stalag/Entities.fgd | 29 | ||||
| -rw-r--r-- | trenchbroom/stalag/GameConfig.cfg | 25 | ||||
| -rw-r--r-- | trenchbroom/stalag/GameEngineProfiles.cfg | 10 |
17 files changed, 1178 insertions, 697 deletions
| @@ -16,7 +16,7 @@ LDFLAGS := ./vendor/$(RAYLIB_VER)/lib/libraylib.a -lm | |||
| 16 | GAME := bin/stalag | 16 | GAME := bin/stalag |
| 17 | HEXDUMP := bin/hexdump | 17 | HEXDUMP := bin/hexdump |
| 18 | PACKER := bin/packer | 18 | PACKER := bin/packer |
| 19 | SOURCES := main.c map.c game.c player.c | 19 | SOURCES := main.c map.c game.c player.c interface.c assets.c menu.c |
| 20 | 20 | ||
| 21 | ifeq ($(SYSTEM), linux_amd64) | 21 | ifeq ($(SYSTEM), linux_amd64) |
| 22 | LDFLAGS += -lX11 | 22 | LDFLAGS += -lX11 |
diff --git a/README.md b/README.md new file mode 100644 index 0000000..802e567 --- /dev/null +++ b/README.md | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | ## Trenchbroom setup | ||
| 2 | |||
| 3 | ```sh | ||
| 4 | ln -s ~/Projects/stalag/trenchbroom/stalag/ ~/.TrenchBroom/games/ | ||
| 5 | ``` | ||
| 6 | |||
| 7 | - Check that `path` in `GameEngineProfiles.cfg` points to game executable. | ||
| 8 | - Open Trenchbroom and go to `View>Preferences` and check that `Game Path` | ||
| 9 | points to project root. | ||
| 10 | |||
| 11 | ## Trenchbroom entities | ||
| 12 | |||
| 13 | - https://developer.valvesoftware.com/wiki/FGD | ||
| @@ -15,122 +15,143 @@ | |||
| 15 | #include <stdbool.h> | 15 | #include <stdbool.h> |
| 16 | #include <stddef.h> | 16 | #include <stddef.h> |
| 17 | 17 | ||
| 18 | // --- Map Structures --- | 18 | #ifndef PLAYER_FOV |
| 19 | #define PLAYER_FOV 90.0f | ||
| 20 | #endif | ||
| 19 | 21 | ||
| 20 | typedef struct { | 22 | typedef struct { |
| 21 | Vector3 p[3]; | 23 | Vector3 p[3]; |
| 22 | char texture[64]; | 24 | char texture[64]; |
| 23 | float shift[2]; | 25 | float shift[2]; |
| 24 | float rotate; | 26 | float rotate; |
| 25 | float scale[2]; | 27 | float scale[2]; |
| 26 | } MapPlane; | 28 | } MapPlane; |
| 27 | 29 | ||
| 28 | typedef struct { | 30 | typedef struct { |
| 29 | MapPlane *planes; | 31 | MapPlane *planes; |
| 30 | int plane_count; | 32 | int plane_count; |
| 31 | } MapBrush; | 33 | } MapBrush; |
| 32 | 34 | ||
| 33 | typedef struct { | 35 | typedef struct { |
| 34 | char key[64]; | 36 | char key[64]; |
| 35 | char value[256]; | 37 | char value[256]; |
| 36 | } MapProperty; | 38 | } MapProperty; |
| 37 | 39 | ||
| 38 | typedef struct { | 40 | typedef struct { |
| 39 | MapProperty *properties; | 41 | MapProperty *properties; |
| 40 | int property_count; | 42 | int property_count; |
| 41 | MapBrush *brushes; | 43 | MapBrush *brushes; |
| 42 | int brush_count; | 44 | int brush_count; |
| 43 | } MapEntity; | 45 | } MapEntity; |
| 44 | 46 | ||
| 45 | typedef struct { | 47 | typedef struct { |
| 46 | MapEntity *entities; | 48 | MapEntity *entities; |
| 47 | int entity_count; | 49 | int entity_count; |
| 48 | } Map; | 50 | } Map; |
| 49 | 51 | ||
| 50 | typedef struct { | 52 | typedef struct { |
| 51 | const char *data; | 53 | const char *data; |
| 52 | size_t length; | 54 | size_t length; |
| 53 | size_t pos; | 55 | size_t pos; |
| 54 | } MapParser; | 56 | } MapParser; |
| 55 | 57 | ||
| 56 | typedef struct { | 58 | typedef struct { |
| 57 | Vector3 normal; | 59 | Vector3 normal; |
| 58 | float dist; | 60 | float dist; |
| 59 | } Plane; | 61 | } Plane; |
| 60 | 62 | ||
| 61 | typedef struct { | 63 | typedef struct { |
| 62 | Vector3 *verts; | 64 | Vector3 *verts; |
| 63 | int count; | 65 | int count; |
| 64 | } Polygon; | 66 | } Polygon; |
| 65 | 67 | ||
| 66 | typedef struct { | 68 | typedef struct { |
| 67 | char texture[64]; | 69 | char texture[64]; |
| 68 | array(float) vertices; | 70 | array(float) vertices; |
| 69 | array(float) texcoords; | 71 | array(float) texcoords; |
| 70 | array(float) normals; | 72 | array(float) normals; |
| 71 | } TextureGroup; | 73 | } TextureGroup; |
| 72 | 74 | ||
| 73 | typedef struct { | 75 | typedef struct { |
| 74 | char name[64]; | 76 | char name[64]; |
| 75 | Texture2D tex; | 77 | Texture2D tex; |
| 76 | } CachedTexture; | 78 | } CachedTexture; |
| 77 | 79 | ||
| 78 | // --- Game State --- | 80 | // Asset Module |
| 81 | Texture2D GetTexture(const char *name); | ||
| 82 | void UnloadAssets(void); | ||
| 83 | Font LoadFontVFS(const char *path, int fontSize); | ||
| 84 | |||
| 85 | // Map Module | ||
| 86 | typedef struct { | ||
| 87 | Model *models; | ||
| 88 | int count; | ||
| 89 | } MapState; | ||
| 79 | 90 | ||
| 91 | bool LoadMap(const char *filename); | ||
| 92 | void UnloadMap(void); | ||
| 93 | bool CheckMapCollision(Vector3 start, Vector3 end, RayCollision *outCollision); | ||
| 94 | Map ParseMap(const char *filename); | ||
| 95 | void FreeMap(Map map); | ||
| 96 | |||
| 97 | // Menu Module | ||
| 98 | void UpdateMenu(void); | ||
| 99 | void DrawMenu(void); | ||
| 100 | |||
| 101 | // Player Module | ||
| 80 | typedef enum { | 102 | typedef enum { |
| 81 | MOVE_NORMAL, | 103 | MOVE_NORMAL, |
| 82 | MOVE_FLY | 104 | MOVE_FLY |
| 83 | } MovementMode; | 105 | } MovementMode; |
| 84 | 106 | ||
| 85 | typedef struct { | 107 | typedef struct { |
| 86 | Camera camera; | 108 | MovementMode move_mode; |
| 87 | Model *world_models; | 109 | Vector3 pos; |
| 88 | int world_model_count; | 110 | Vector3 velocity; |
| 89 | bool cursor_captured; | 111 | bool is_grounded; |
| 90 | bool vsync; | 112 | float yaw; |
| 91 | Font font_ui; | 113 | float pitch; |
| 92 | 114 | float lean_amount; | |
| 93 | MovementMode move_mode; | 115 | float crouch_amount; |
| 94 | Vector3 pos; | 116 | float horizontal_speed; |
| 95 | Vector3 velocity; | 117 | } PlayerState; |
| 96 | bool is_grounded; | 118 | |
| 97 | float yaw; | 119 | void UpdatePlayer(void); |
| 98 | float pitch; | 120 | |
| 99 | float lean_amount; | 121 | // Game State Module |
| 100 | float crouch_amount; | 122 | typedef enum { |
| 101 | float horizontal_speed; | 123 | STATE_MENU, |
| 124 | STATE_PLAYING | ||
| 125 | } GameStateMode; | ||
| 126 | |||
| 127 | typedef struct { | ||
| 128 | GameStateMode mode; | ||
| 129 | char map_path[256]; | ||
| 130 | Camera camera; | ||
| 131 | bool cursor_captured; | ||
| 132 | bool vsync; | ||
| 133 | Font font_ui; | ||
| 134 | |||
| 135 | PlayerState player; | ||
| 136 | MapState map; | ||
| 102 | } GameState; | 137 | } GameState; |
| 103 | 138 | ||
| 104 | extern GameState game; | 139 | extern GameState game; |
| 105 | 140 | ||
| 106 | // --- Prototypes --- | 141 | void InitGame(void); |
| 107 | 142 | void SetMap(const char *path); | |
| 108 | // Map | 143 | void UpdateGame(void); |
| 109 | char map_peek(MapParser *p); | 144 | void DrawGame(void); |
| 110 | char map_get(MapParser *p); | ||
| 111 | void map_skip_whitespace(MapParser *p); | ||
| 112 | bool map_expect(MapParser *p, char expected); | ||
| 113 | void map_parse_token(MapParser *p, char *buffer, int size); | ||
| 114 | Vector3 map_parse_vector(MapParser *p); | ||
| 115 | Map ParseMap(const char *filename); | ||
| 116 | void FreeMap(Map map); | ||
| 117 | 145 | ||
| 118 | // Geometry | 146 | // Geometry Helpers |
| 119 | Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3); | 147 | Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3); |
| 120 | void PolyFree(Polygon p); | 148 | void PolyFree(Polygon p); |
| 121 | Polygon PolyClip(Polygon poly, Plane plane); | 149 | Polygon PolyClip(Polygon poly, Plane plane); |
| 122 | Polygon CreateLargeQuad(Plane plane); | 150 | Polygon CreateLargeQuad(Plane plane); |
| 123 | Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane); | 151 | Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane); |
| 124 | 152 | ||
| 125 | // Game | 153 | // Interface |
| 126 | Texture2D GetTexture(const char *name); | 154 | void DrawCrosshair(void); |
| 127 | void InitGame(void); | 155 | void DrawDebugInfo(void); |
| 128 | void UpdateGame(void); | ||
| 129 | void DrawGame(void); | ||
| 130 | bool LoadMap(const char *filename); | ||
| 131 | void UnloadMap(void); | ||
| 132 | |||
| 133 | // Player | ||
| 134 | void UpdatePlayer(void); | ||
| 135 | 156 | ||
| 136 | #endif | 157 | #endif |
diff --git a/assets.c b/assets.c new file mode 100644 index 0000000..0b1f66c --- /dev/null +++ b/assets.c | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | #include "all.h" | ||
| 2 | |||
| 3 | #include <stdio.h> | ||
| 4 | #include <string.h> | ||
| 5 | #include <ctype.h> | ||
| 6 | |||
| 7 | static array(CachedTexture) texture_cache; | ||
| 8 | |||
| 9 | Texture2D GetTexture(const char *name) { | ||
| 10 | if (texture_cache.data == NULL) { | ||
| 11 | array_init(texture_cache); | ||
| 12 | } | ||
| 13 | |||
| 14 | for (int i = 0; i < texture_cache.length; i++) { | ||
| 15 | if (strcmp(texture_cache.data[i].name, name) == 0) return texture_cache.data[i].tex; | ||
| 16 | } | ||
| 17 | |||
| 18 | char lname[256]; | ||
| 19 | strncpy(lname, name, sizeof(lname)); | ||
| 20 | lname[sizeof(lname)-1] = '\0'; | ||
| 21 | for (int i = 0; lname[i]; i++) lname[i] = tolower(lname[i]); | ||
| 22 | |||
| 23 | char path[256]; | ||
| 24 | void *data = NULL; | ||
| 25 | size_t size = 0; | ||
| 26 | const char *ext = ".png"; | ||
| 27 | |||
| 28 | snprintf(path, sizeof(path), "textures/%s.png", lname); | ||
| 29 | data = vfs_read(path, &size); | ||
| 30 | if (!data) { | ||
| 31 | snprintf(path, sizeof(path), "textures/%s.jpg", lname); | ||
| 32 | data = vfs_read(path, &size); | ||
| 33 | ext = ".jpg"; | ||
| 34 | } | ||
| 35 | |||
| 36 | Texture2D tex = { 0 }; | ||
| 37 | if (data) { | ||
| 38 | Image img = LoadImageFromMemory(ext, data, (int)size); | ||
| 39 | if (img.data) { | ||
| 40 | tex = LoadTextureFromImage(img); | ||
| 41 | UnloadImage(img); | ||
| 42 | } | ||
| 43 | vfs_free(data); | ||
| 44 | } | ||
| 45 | |||
| 46 | if (tex.id == 0) { | ||
| 47 | TraceLog(LOG_WARNING, "Failed to load texture: '%s'", name); | ||
| 48 | Image img = GenImageChecked(64, 64, 8, 8, (Color){128, 128, 128, 255}, (Color){200, 200, 200, 255}); | ||
| 49 | tex = LoadTextureFromImage(img); | ||
| 50 | UnloadImage(img); | ||
| 51 | } else { | ||
| 52 | GenTextureMipmaps(&tex); | ||
| 53 | SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); | ||
| 54 | SetTextureWrap(tex, TEXTURE_WRAP_REPEAT); | ||
| 55 | } | ||
| 56 | |||
| 57 | CachedTexture cached; | ||
| 58 | strncpy(cached.name, name, sizeof(cached.name)); | ||
| 59 | cached.tex = tex; | ||
| 60 | array_push(texture_cache, cached); | ||
| 61 | |||
| 62 | return tex; | ||
| 63 | } | ||
| 64 | |||
| 65 | void UnloadAssets(void) { | ||
| 66 | for (int i = 0; i < texture_cache.length; i++) { | ||
| 67 | UnloadTexture(texture_cache.data[i].tex); | ||
| 68 | } | ||
| 69 | array_clear(texture_cache); | ||
| 70 | } | ||
| 71 | |||
| 72 | Font LoadFontVFS(const char *path, int fontSize) { | ||
| 73 | size_t size = 0; | ||
| 74 | void *data = vfs_read(path, &size); | ||
| 75 | if (data) { | ||
| 76 | Font font = LoadFontFromMemory(".ttf", data, (int)size, fontSize, NULL, 0); | ||
| 77 | vfs_free(data); | ||
| 78 | return font; | ||
| 79 | } | ||
| 80 | return GetFontDefault(); | ||
| 81 | } | ||
| @@ -5,6 +5,11 @@ | |||
| 5 | #define WINDOW_HEIGHT 720 | 5 | #define WINDOW_HEIGHT 720 |
| 6 | #define WINDOW_TITLE "Stalag" | 6 | #define WINDOW_TITLE "Stalag" |
| 7 | 7 | ||
| 8 | #define VFS_DATA_PAK "data.pak" | ||
| 9 | |||
| 10 | #define UI_FONT_PATH "fonts/LiberationSans-Bold.ttf" | ||
| 11 | #define UI_FONT_SIZE 20 | ||
| 12 | |||
| 8 | #define PLAYER_MOVE_SPEED 200.0f | 13 | #define PLAYER_MOVE_SPEED 200.0f |
| 9 | #define PLAYER_FLY_SPEED 400.0f | 14 | #define PLAYER_FLY_SPEED 400.0f |
| 10 | #define PLAYER_ROTATION_SPEED 0.05f | 15 | #define PLAYER_ROTATION_SPEED 0.05f |
| @@ -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 | ||
| 6 | GameState game; | 5 | GameState game; |
| 7 | static array(CachedTexture) texture_cache; | ||
| 8 | |||
| 9 | Texture2D 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) { | 7 | void 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 | ||
| 64 | void UnloadMap(void) { | 26 | void 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 | ||
| 80 | bool LoadMap(const char *filename) { | 31 | void 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 | |||
| 226 | void 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 | ||
| 251 | void UpdateGame(void) { | 52 | void 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 | ||
| 275 | void 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 | } |
diff --git a/interface.c b/interface.c new file mode 100644 index 0000000..58d8721 --- /dev/null +++ b/interface.c | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | #include "all.h" | ||
| 2 | |||
| 3 | void DrawCrosshair(void) { | ||
| 4 | int screenWidth = GetScreenWidth(); | ||
| 5 | int screenHeight = GetScreenHeight(); | ||
| 6 | DrawLine(screenWidth / 2 - 10, screenHeight / 2, screenWidth / 2 + 10, screenHeight / 2, GREEN); | ||
| 7 | DrawLine(screenWidth / 2, screenHeight / 2 - 10, screenWidth / 2, screenHeight / 2 + 10, GREEN); | ||
| 8 | } | ||
| 9 | |||
| 10 | void DrawDebugInfo(void) { | ||
| 11 | DrawTextEx(game.font_ui, TextFormat("%i FPS", GetFPS()), (Vector2){ 10, 10 }, 20, 2, GREEN); | ||
| 12 | DrawTextEx(game.font_ui, TextFormat("VSync: %s", game.vsync ? "ON" : "OFF"), (Vector2){ 10, 35 }, 20, 2, GREEN); | ||
| 13 | DrawTextEx(game.font_ui, TextFormat("Speed: %.0f", game.player.horizontal_speed), (Vector2){ 10, 60 }, 20, 2, GREEN); | ||
| 14 | } | ||
| @@ -1,29 +1,97 @@ | |||
| 1 | #define _POSIX_C_SOURCE 200809L | ||
| 2 | #define NONSTD_IMPLEMENTATION | 1 | #define NONSTD_IMPLEMENTATION |
| 3 | #define VFS_IMPLEMENTATION | 2 | #define VFS_IMPLEMENTATION |
| 4 | #include "all.h" | 3 | #include "all.h" |
| 5 | 4 | ||
| 6 | int main(void) { | 5 | #include <getopt.h> |
| 7 | SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI); | ||
| 8 | InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE); | ||
| 9 | 6 | ||
| 10 | int monitor = GetCurrentMonitor(); | 7 | int main(int argc, char *argv[]) { |
| 11 | SetWindowPosition((GetMonitorWidth(monitor) - GetScreenWidth()) / 2, | 8 | stringb map_path; |
| 12 | (GetMonitorHeight(monitor) - GetScreenHeight()) / 2); | 9 | sb_init(&map_path, 256); |
| 13 | 10 | sb_append_cstr(&map_path, "maps/demo3.map"); | |
| 14 | SetTargetFPS(GetMonitorRefreshRate(monitor)); | ||
| 15 | 11 | ||
| 16 | vfs_init("data.pak"); | 12 | bool skip_title = false; |
| 17 | InitGame(); | ||
| 18 | game.vsync = true; | ||
| 19 | 13 | ||
| 20 | while (!WindowShouldClose()) { | 14 | static struct option long_options[] = { |
| 21 | UpdateGame(); | 15 | {"map", required_argument, 0, 'm'}, |
| 22 | DrawGame(); | 16 | {0, 0, 0, 0} |
| 23 | } | 17 | }; |
| 24 | 18 | ||
| 25 | vfs_shutdown(); | 19 | int opt; |
| 26 | CloseWindow(); | 20 | int option_index = 0; |
| 21 | while ((opt = getopt_long_only(argc, argv, "m:", long_options, &option_index)) != -1) { | ||
| 22 | switch (opt) { | ||
| 23 | case 'm': | ||
| 24 | sb_free(&map_path); | ||
| 25 | sb_init(&map_path, 256); | ||
| 26 | sb_append_cstr(&map_path, optarg); | ||
| 27 | skip_title = true; | ||
| 28 | break; | ||
| 29 | } | ||
| 30 | } | ||
| 27 | 31 | ||
| 28 | return 0; | 32 | SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI); |
| 33 | InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE); | ||
| 34 | |||
| 35 | int monitor = GetCurrentMonitor(); | ||
| 36 | SetWindowPosition((GetMonitorWidth(monitor) - GetScreenWidth()) / 2, (GetMonitorHeight(monitor) - GetScreenHeight()) / 2); | ||
| 37 | SetTargetFPS(GetMonitorRefreshRate(monitor)); | ||
| 38 | |||
| 39 | vfs_init(VFS_DATA_PAK); | ||
| 40 | InitGame(); | ||
| 41 | SetMap(map_path.data); | ||
| 42 | |||
| 43 | if (skip_title) { | ||
| 44 | if (LoadMap(game.map_path)) { | ||
| 45 | game.mode = STATE_PLAYING; | ||
| 46 | game.cursor_captured = true; | ||
| 47 | DisableCursor(); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | game.vsync = true; | ||
| 52 | |||
| 53 | while (!WindowShouldClose()) { | ||
| 54 | // Global Map Switching (Debug/Demo) | ||
| 55 | if (IsKeyPressed(KEY_ONE)) { | ||
| 56 | if (LoadMap("maps/demo1.map")) { | ||
| 57 | game.mode = STATE_PLAYING; | ||
| 58 | game.cursor_captured = true; | ||
| 59 | DisableCursor(); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | if (IsKeyPressed(KEY_TWO)) { | ||
| 63 | if (LoadMap("maps/demo2.map")) { | ||
| 64 | game.mode = STATE_PLAYING; | ||
| 65 | game.cursor_captured = true; | ||
| 66 | DisableCursor(); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | if (IsKeyPressed(KEY_THREE)) { | ||
| 70 | if (LoadMap("maps/demo3.map")) { | ||
| 71 | game.mode = STATE_PLAYING; | ||
| 72 | game.cursor_captured = true; | ||
| 73 | DisableCursor(); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | // Game State Switcher | ||
| 78 | switch (game.mode) { | ||
| 79 | case STATE_MENU: | ||
| 80 | UpdateMenu(); | ||
| 81 | DrawMenu(); | ||
| 82 | break; | ||
| 83 | case STATE_PLAYING: | ||
| 84 | UpdateGame(); | ||
| 85 | DrawGame(); | ||
| 86 | break; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | UnloadMap(); | ||
| 91 | UnloadAssets(); | ||
| 92 | vfs_shutdown(); | ||
| 93 | CloseWindow(); | ||
| 94 | sb_free(&map_path); | ||
| 95 | |||
| 96 | return 0; | ||
| 29 | } | 97 | } |
| @@ -1,246 +1,437 @@ | |||
| 1 | #include "all.h" | 1 | #include "all.h" |
| 2 | |||
| 2 | #include <ctype.h> | 3 | #include <ctype.h> |
| 3 | #include <stdlib.h> | 4 | #include <stdlib.h> |
| 4 | #include <stdio.h> | 5 | #include <stdio.h> |
| 6 | #include <string.h> | ||
| 7 | #include <float.h> | ||
| 5 | 8 | ||
| 6 | char map_peek(MapParser *p) { | 9 | char map_peek(MapParser *p) { |
| 7 | if (p->pos >= p->length) return 0; | 10 | if (p->pos >= p->length) return 0; |
| 8 | return p->data[p->pos]; | 11 | return p->data[p->pos]; |
| 9 | } | 12 | } |
| 10 | 13 | ||
| 11 | char map_get(MapParser *p) { | 14 | char map_get(MapParser *p) { |
| 12 | if (p->pos >= p->length) return 0; | 15 | if (p->pos >= p->length) return 0; |
| 13 | return p->data[p->pos++]; | 16 | return p->data[p->pos++]; |
| 14 | } | 17 | } |
| 15 | 18 | ||
| 16 | void map_skip_whitespace(MapParser *p) { | 19 | void map_skip_whitespace(MapParser *p) { |
| 17 | while (true) { | 20 | while (true) { |
| 18 | char c = map_peek(p); | 21 | char c = map_peek(p); |
| 19 | if (isspace(c)) { | 22 | if (isspace(c)) { |
| 20 | map_get(p); | 23 | map_get(p); |
| 21 | } else if (c == '/' && p->data[p->pos + 1] == '/') { | 24 | } else if (c == '/' && p->data[p->pos + 1] == '/') { |
| 22 | while (map_peek(p) != '\n' && map_peek(p) != 0) map_get(p); | 25 | while (map_peek(p) != '\n' && map_peek(p) != 0) map_get(p); |
| 23 | } else { | 26 | } else { |
| 24 | break; | 27 | break; |
| 25 | } | 28 | } |
| 26 | } | 29 | } |
| 27 | } | 30 | } |
| 28 | 31 | ||
| 29 | bool map_expect(MapParser *p, char expected) { | 32 | bool map_expect(MapParser *p, char expected) { |
| 30 | map_skip_whitespace(p); | 33 | map_skip_whitespace(p); |
| 31 | if (map_get(p) == expected) return true; | 34 | if (map_get(p) == expected) return true; |
| 32 | return false; | 35 | return false; |
| 33 | } | 36 | } |
| 34 | 37 | ||
| 35 | void map_parse_token(MapParser *p, char *buffer, int size) { | 38 | void map_parse_token(MapParser *p, char *buffer, int size) { |
| 36 | map_skip_whitespace(p); | 39 | map_skip_whitespace(p); |
| 37 | int i = 0; | 40 | int i = 0; |
| 38 | bool quoted = false; | 41 | bool quoted = false; |
| 39 | if (map_peek(p) == '"') { | 42 | if (map_peek(p) == '"') { |
| 40 | quoted = true; | 43 | quoted = true; |
| 41 | map_get(p); | 44 | map_get(p); |
| 42 | } | 45 | } |
| 43 | 46 | ||
| 44 | while (true) { | 47 | while (true) { |
| 45 | char c = map_peek(p); | 48 | char c = map_peek(p); |
| 46 | if (c == 0) break; | 49 | if (c == 0) break; |
| 47 | if (quoted) { | 50 | if (quoted) { |
| 48 | if (c == '"') { | 51 | if (c == '"') { |
| 49 | map_get(p); | 52 | map_get(p); |
| 50 | break; | 53 | break; |
| 51 | } | 54 | } |
| 52 | } else { | 55 | } else { |
| 53 | if (isspace(c) || c == '(' || c == ')' || c == '{' || c == '}') break; | 56 | if (isspace(c) || c == '(' || c == ')' || c == '{' || c == '}') break; |
| 54 | } | 57 | } |
| 55 | if (i < size - 1) buffer[i++] = map_get(p); | 58 | if (i < size - 1) buffer[i++] = map_get(p); |
| 56 | else map_get(p); | 59 | else map_get(p); |
| 57 | } | 60 | } |
| 58 | buffer[i] = 0; | 61 | buffer[i] = 0; |
| 59 | } | 62 | } |
| 60 | 63 | ||
| 61 | Vector3 map_parse_vector(MapParser *p) { | 64 | Vector3 map_parse_vector(MapParser *p) { |
| 62 | map_expect(p, '('); | 65 | map_expect(p, '('); |
| 63 | char buf[64]; | 66 | char buf[64]; |
| 64 | map_parse_token(p, buf, sizeof(buf)); | 67 | map_parse_token(p, buf, sizeof(buf)); |
| 65 | float x = (float)atof(buf); | 68 | float x = (float)atof(buf); |
| 66 | map_parse_token(p, buf, sizeof(buf)); | 69 | map_parse_token(p, buf, sizeof(buf)); |
| 67 | float y = (float)atof(buf); | 70 | float y = (float)atof(buf); |
| 68 | map_parse_token(p, buf, sizeof(buf)); | 71 | map_parse_token(p, buf, sizeof(buf)); |
| 69 | float z = (float)atof(buf); | 72 | float z = (float)atof(buf); |
| 70 | map_expect(p, ')'); | 73 | map_expect(p, ')'); |
| 71 | return (Vector3){ x, z, -y }; | 74 | return (Vector3){ x, z, -y }; |
| 72 | } | 75 | } |
| 73 | 76 | ||
| 74 | Map ParseMap(const char *filename) { | 77 | Map ParseMap(const char *filename) { |
| 75 | size_t size; | 78 | size_t size; |
| 76 | char *data = (char *)vfs_read(filename, &size); | 79 | char *data = (char *)vfs_read(filename, &size); |
| 77 | Map map = { 0 }; | 80 | Map map = { 0 }; |
| 78 | if (!data) return map; | 81 | if (!data) return map; |
| 79 | 82 | ||
| 80 | MapParser p = { data, size, 0 }; | 83 | MapParser p = { data, size, 0 }; |
| 81 | array(MapEntity) entities; | 84 | array(MapEntity) entities; |
| 82 | array_init(entities); | 85 | array_init(entities); |
| 83 | 86 | ||
| 84 | while (true) { | 87 | while (true) { |
| 85 | map_skip_whitespace(&p); | 88 | map_skip_whitespace(&p); |
| 86 | if (map_peek(&p) == 0) break; | 89 | if (map_peek(&p) == 0) break; |
| 87 | 90 | ||
| 88 | if (map_expect(&p, '{')) { | 91 | if (map_expect(&p, '{')) { |
| 89 | MapEntity entity = { 0 }; | 92 | MapEntity entity = { 0 }; |
| 90 | array(MapProperty) props; | 93 | array(MapProperty) props; |
| 91 | array(MapBrush) brushes; | 94 | array(MapBrush) brushes; |
| 92 | array_init(props); | 95 | array_init(props); |
| 93 | array_init(brushes); | 96 | array_init(brushes); |
| 94 | 97 | ||
| 95 | while (true) { | 98 | while (true) { |
| 96 | map_skip_whitespace(&p); | 99 | map_skip_whitespace(&p); |
| 97 | char c = map_peek(&p); | 100 | char c = map_peek(&p); |
| 98 | if (c == '}') { | 101 | if (c == '}') { |
| 99 | map_get(&p); | 102 | map_get(&p); |
| 100 | break; | 103 | break; |
| 101 | } else if (c == '"') { | 104 | } else if (c == '"') { |
| 102 | MapProperty prop; | 105 | MapProperty prop; |
| 103 | map_parse_token(&p, prop.key, sizeof(prop.key)); | 106 | map_parse_token(&p, prop.key, sizeof(prop.key)); |
| 104 | map_parse_token(&p, prop.value, sizeof(prop.value)); | 107 | map_parse_token(&p, prop.value, sizeof(prop.value)); |
| 105 | array_push(props, prop); | 108 | array_push(props, prop); |
| 106 | } else if (c == '{') { | 109 | } else if (c == '{') { |
| 107 | map_get(&p); | 110 | map_get(&p); |
| 108 | MapBrush brush = { 0 }; | 111 | MapBrush brush = { 0 }; |
| 109 | array(MapPlane) planes; | 112 | array(MapPlane) planes; |
| 110 | array_init(planes); | 113 | array_init(planes); |
| 111 | while (true) { | 114 | while (true) { |
| 112 | map_skip_whitespace(&p); | 115 | map_skip_whitespace(&p); |
| 113 | if (map_peek(&p) == '}') { | 116 | if (map_peek(&p) == '}') { |
| 114 | map_get(&p); | 117 | map_get(&p); |
| 115 | break; | 118 | break; |
| 116 | } | 119 | } |
| 117 | MapPlane plane; | 120 | MapPlane plane; |
| 118 | plane.p[0] = map_parse_vector(&p); | 121 | plane.p[0] = map_parse_vector(&p); |
| 119 | plane.p[1] = map_parse_vector(&p); | 122 | plane.p[1] = map_parse_vector(&p); |
| 120 | plane.p[2] = map_parse_vector(&p); | 123 | plane.p[2] = map_parse_vector(&p); |
| 121 | map_parse_token(&p, plane.texture, sizeof(plane.texture)); | 124 | map_parse_token(&p, plane.texture, sizeof(plane.texture)); |
| 122 | 125 | ||
| 123 | char buf[64]; | 126 | char buf[64]; |
| 124 | map_parse_token(&p, buf, sizeof(buf)); plane.shift[0] = (float)atof(buf); | 127 | 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); | 128 | 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); | 129 | 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); | 130 | 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); | 131 | map_parse_token(&p, buf, sizeof(buf)); plane.scale[1] = (float)atof(buf); |
| 129 | 132 | ||
| 130 | array_push(planes, plane); | 133 | array_push(planes, plane); |
| 131 | } | 134 | } |
| 132 | brush.planes = planes.data; | 135 | brush.planes = planes.data; |
| 133 | brush.plane_count = (int)planes.length; | 136 | brush.plane_count = (int)planes.length; |
| 134 | array_push(brushes, brush); | 137 | array_push(brushes, brush); |
| 135 | } else { | 138 | } else { |
| 136 | map_get(&p); | 139 | map_get(&p); |
| 137 | } | 140 | } |
| 138 | } | 141 | } |
| 139 | entity.properties = props.data; | 142 | entity.properties = props.data; |
| 140 | entity.property_count = (int)props.length; | 143 | entity.property_count = (int)props.length; |
| 141 | entity.brushes = brushes.data; | 144 | entity.brushes = brushes.data; |
| 142 | entity.brush_count = (int)brushes.length; | 145 | entity.brush_count = (int)brushes.length; |
| 143 | array_push(entities, entity); | 146 | array_push(entities, entity); |
| 144 | } | 147 | } |
| 145 | } | 148 | } |
| 146 | 149 | ||
| 147 | map.entities = entities.data; | 150 | map.entities = entities.data; |
| 148 | map.entity_count = (int)entities.length; | 151 | map.entity_count = (int)entities.length; |
| 149 | vfs_free(data); | 152 | vfs_free(data); |
| 150 | return map; | 153 | return map; |
| 151 | } | 154 | } |
| 152 | 155 | ||
| 153 | void FreeMap(Map map) { | 156 | void FreeMap(Map map) { |
| 154 | for (int i = 0; i < map.entity_count; i++) { | 157 | for (int i = 0; i < map.entity_count; i++) { |
| 155 | MapEntity *e = &map.entities[i]; | 158 | MapEntity *e = &map.entities[i]; |
| 156 | if (e->properties) free(e->properties); | 159 | if (e->properties) free(e->properties); |
| 157 | for (int j = 0; j < e->brush_count; j++) { | 160 | for (int j = 0; j < e->brush_count; j++) { |
| 158 | if (e->brushes[j].planes) free(e->brushes[j].planes); | 161 | if (e->brushes[j].planes) free(e->brushes[j].planes); |
| 159 | } | 162 | } |
| 160 | if (e->brushes) free(e->brushes); | 163 | if (e->brushes) free(e->brushes); |
| 161 | } | 164 | } |
| 162 | if (map.entities) free(map.entities); | 165 | if (map.entities) free(map.entities); |
| 163 | } | 166 | } |
| 164 | 167 | ||
| 165 | Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3) { | 168 | Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3) { |
| 166 | Vector3 v1 = Vector3Subtract(p2, p1); | 169 | Vector3 v1 = Vector3Subtract(p2, p1); |
| 167 | Vector3 v2 = Vector3Subtract(p3, p1); | 170 | Vector3 v2 = Vector3Subtract(p3, p1); |
| 168 | Vector3 normal = Vector3Normalize(Vector3CrossProduct(v1, v2)); | 171 | Vector3 normal = Vector3Normalize(Vector3CrossProduct(v1, v2)); |
| 169 | return (Plane){ normal, Vector3DotProduct(normal, p1) }; | 172 | return (Plane){ normal, Vector3DotProduct(normal, p1) }; |
| 170 | } | 173 | } |
| 171 | 174 | ||
| 172 | void PolyFree(Polygon p) { | 175 | void PolyFree(Polygon p) { |
| 173 | if (p.verts) free(p.verts); | 176 | if (p.verts) free(p.verts); |
| 174 | } | 177 | } |
| 175 | 178 | ||
| 176 | Polygon PolyClip(Polygon poly, Plane plane) { | 179 | Polygon PolyClip(Polygon poly, Plane plane) { |
| 177 | if (poly.count == 0) return poly; | 180 | if (poly.count == 0) return poly; |
| 178 | 181 | ||
| 179 | array(Vector3) out_verts; | 182 | array(Vector3) out_verts; |
| 180 | array_init(out_verts); | 183 | array_init(out_verts); |
| 181 | 184 | ||
| 182 | for (int i = 0; i < poly.count; i++) { | 185 | for (int i = 0; i < poly.count; i++) { |
| 183 | Vector3 p1 = poly.verts[i]; | 186 | Vector3 p1 = poly.verts[i]; |
| 184 | Vector3 p2 = poly.verts[(i + 1) % poly.count]; | 187 | Vector3 p2 = poly.verts[(i + 1) % poly.count]; |
| 185 | 188 | ||
| 186 | float d1 = Vector3DotProduct(plane.normal, p1) - plane.dist; | 189 | float d1 = Vector3DotProduct(plane.normal, p1) - plane.dist; |
| 187 | float d2 = Vector3DotProduct(plane.normal, p2) - plane.dist; | 190 | float d2 = Vector3DotProduct(plane.normal, p2) - plane.dist; |
| 188 | 191 | ||
| 189 | // Using a smaller epsilon and more robust logic | 192 | const float eps = 0.001f; |
| 190 | const float eps = 0.001f; | 193 | |
| 191 | 194 | if (d1 >= -eps) { | |
| 192 | if (d1 >= -eps) { | 195 | if (d2 >= -eps) { |
| 193 | if (d2 >= -eps) { | 196 | array_push(out_verts, p2); |
| 194 | array_push(out_verts, p2); | 197 | } else { |
| 195 | } else { | 198 | float t = d1 / (d1 - d2); |
| 196 | float t = d1 / (d1 - d2); | 199 | Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t)); |
| 197 | Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t)); | 200 | array_push(out_verts, intersect); |
| 198 | array_push(out_verts, intersect); | 201 | } |
| 199 | } | 202 | } else { |
| 200 | } else { | 203 | if (d2 >= -eps) { |
| 201 | if (d2 >= -eps) { | 204 | float t = d1 / (d1 - d2); |
| 202 | float t = d1 / (d1 - d2); | 205 | Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t)); |
| 203 | Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t)); | 206 | array_push(out_verts, intersect); |
| 204 | array_push(out_verts, intersect); | 207 | array_push(out_verts, p2); |
| 205 | array_push(out_verts, p2); | 208 | } |
| 206 | } | 209 | } |
| 207 | } | 210 | } |
| 208 | } | 211 | |
| 209 | 212 | Polygon res; | |
| 210 | Polygon res; | 213 | res.verts = out_verts.data; |
| 211 | res.verts = out_verts.data; | 214 | res.count = (int)out_verts.length; |
| 212 | res.count = (int)out_verts.length; | 215 | return res; |
| 213 | return res; | ||
| 214 | } | 216 | } |
| 215 | 217 | ||
| 216 | Polygon CreateLargeQuad(Plane plane) { | 218 | Polygon CreateLargeQuad(Plane plane) { |
| 217 | Vector3 n = plane.normal; | 219 | Vector3 n = plane.normal; |
| 218 | Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 }; | 220 | Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 }; |
| 219 | Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n)); | 221 | Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n)); |
| 220 | up = Vector3CrossProduct(n, right); | 222 | up = Vector3CrossProduct(n, right); |
| 221 | 223 | ||
| 222 | Vector3 center = Vector3Scale(n, plane.dist); | 224 | Vector3 center = Vector3Scale(n, plane.dist); |
| 223 | float size = 10000.0f; | 225 | float size = 10000.0f; |
| 224 | 226 | ||
| 225 | Polygon poly; | 227 | Polygon poly; |
| 226 | poly.verts = ALLOC(Vector3, 4); | 228 | poly.verts = ALLOC(Vector3, 4); |
| 227 | poly.count = 4; | 229 | poly.count = 4; |
| 228 | poly.verts[0] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, size))); | 230 | 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))); | 231 | 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))); | 232 | 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))); | 233 | poly.verts[3] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, -size))); |
| 232 | 234 | ||
| 233 | return poly; | 235 | return poly; |
| 234 | } | 236 | } |
| 235 | 237 | ||
| 236 | Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane) { | 238 | Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane) { |
| 237 | Vector3 n = plane.normal; | 239 | Vector3 n = plane.normal; |
| 238 | Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 }; | 240 | Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 }; |
| 239 | Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n)); | 241 | Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n)); |
| 240 | up = Vector3CrossProduct(n, right); | 242 | up = Vector3CrossProduct(n, right); |
| 241 | 243 | ||
| 242 | float u = Vector3DotProduct(p, right) * (1.0f / (mp->scale[0] ? mp->scale[0] : 1.0f)) + mp->shift[0]; | 244 | 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]; | 245 | float v = Vector3DotProduct(p, up) * (1.0f / (mp->scale[1] ? mp->scale[1] : 1.0f)) + mp->shift[1]; |
| 244 | 246 | ||
| 245 | return (Vector2){ u / 64.0f, v / 64.0f }; | 247 | return (Vector2){ u / 64.0f, v / 64.0f }; |
| 248 | } | ||
| 249 | |||
| 250 | void UnloadMap(void) { | ||
| 251 | if (game.map.models) { | ||
| 252 | for (int i = 0; i < game.map.count; i++) { | ||
| 253 | UnloadModel(game.map.models[i]); | ||
| 254 | } | ||
| 255 | free(game.map.models); | ||
| 256 | game.map.models = NULL; | ||
| 257 | game.map.count = 0; | ||
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | bool LoadMap(const char *filename) { | ||
| 262 | TraceLog(LOG_INFO, "Loading map: %s", filename); | ||
| 263 | |||
| 264 | Map map = ParseMap(filename); | ||
| 265 | if (map.entity_count == 0) { | ||
| 266 | TraceLog(LOG_ERROR, "Failed to load map or map is empty: %s", filename); | ||
| 267 | return false; | ||
| 268 | } | ||
| 269 | |||
| 270 | UnloadMap(); | ||
| 271 | |||
| 272 | array(TextureGroup) groups; | ||
| 273 | array_init(groups); | ||
| 274 | |||
| 275 | // Default player state if no start found | ||
| 276 | game.player.pos = (Vector3){ 0, 10, 0 }; | ||
| 277 | game.player.yaw = 0; | ||
| 278 | game.player.pitch = 0; | ||
| 279 | |||
| 280 | int total_brushes = 0; | ||
| 281 | for (int i = 0; i < map.entity_count; i++) { | ||
| 282 | MapEntity *e = &map.entities[i]; | ||
| 283 | bool is_world = false; | ||
| 284 | const char *classname = ""; | ||
| 285 | |||
| 286 | for (int j = 0; j < e->property_count; j++) { | ||
| 287 | if (strcmp(e->properties[j].key, "classname") == 0) { | ||
| 288 | classname = e->properties[j].value; | ||
| 289 | if (strcmp(classname, "worldspawn") == 0) is_world = true; | ||
| 290 | } | ||
| 291 | } | ||
| 292 | |||
| 293 | // Handle entities (like player start) | ||
| 294 | for (int j = 0; j < e->property_count; j++) { | ||
| 295 | if (strcmp(e->properties[j].key, "origin") == 0) { | ||
| 296 | float x, y, z; | ||
| 297 | sscanf(e->properties[j].value, "%f %f %f", &x, &y, &z); | ||
| 298 | if (strcmp(classname, "info_player_start") == 0) { | ||
| 299 | game.player.pos = (Vector3){ x, z, -y }; | ||
| 300 | float angle = 0; | ||
| 301 | for (int k = 0; k < e->property_count; k++) { | ||
| 302 | if (strcmp(e->properties[k].key, "angle") == 0) { | ||
| 303 | angle = (float)atof(e->properties[k].value); | ||
| 304 | } | ||
| 305 | } | ||
| 306 | game.player.yaw = (angle + 90.0f) * DEG2RAD; | ||
| 307 | game.player.pitch = 0; | ||
| 308 | TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f", game.player.pos.x, game.player.pos.y, game.player.pos.z); | ||
| 309 | } | ||
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 | if (is_world) { | ||
| 314 | total_brushes += e->brush_count; | ||
| 315 | for (int j = 0; j < e->brush_count; j++) { | ||
| 316 | MapBrush *brush = &e->brushes[j]; | ||
| 317 | for (int p_idx = 0; p_idx < brush->plane_count; p_idx++) { | ||
| 318 | MapPlane *mp = &brush->planes[p_idx]; | ||
| 319 | Plane plane = PlaneFromPoints(mp->p[0], mp->p[1], mp->p[2]); | ||
| 320 | |||
| 321 | if (Vector3Length(plane.normal) < 0.5f) continue; | ||
| 322 | |||
| 323 | Polygon poly = CreateLargeQuad(plane); | ||
| 324 | for (int k = 0; k < brush->plane_count; k++) { | ||
| 325 | if (p_idx == k) continue; | ||
| 326 | Plane clipPlane = PlaneFromPoints(brush->planes[k].p[0], brush->planes[k].p[1], brush->planes[k].p[2]); | ||
| 327 | if (Vector3Length(clipPlane.normal) < 0.5f) continue; | ||
| 328 | |||
| 329 | Polygon next = PolyClip(poly, clipPlane); | ||
| 330 | PolyFree(poly); | ||
| 331 | poly = next; | ||
| 332 | } | ||
| 333 | |||
| 334 | if (poly.count >= 3) { | ||
| 335 | TextureGroup *group = NULL; | ||
| 336 | for (int g = 0; g < groups.length; g++) { | ||
| 337 | if (strcmp(groups.data[g].texture, mp->texture) == 0) { | ||
| 338 | group = &groups.data[g]; | ||
| 339 | break; | ||
| 340 | } | ||
| 341 | } | ||
| 342 | if (!group) { | ||
| 343 | TextureGroup new_group = { 0 }; | ||
| 344 | strncpy(new_group.texture, mp->texture, sizeof(new_group.texture)); | ||
| 345 | array_init(new_group.vertices); | ||
| 346 | array_init(new_group.texcoords); | ||
| 347 | array_init(new_group.normals); | ||
| 348 | array_push(groups, new_group); | ||
| 349 | group = &groups.data[groups.length - 1]; | ||
| 350 | } | ||
| 351 | for (int v = 1; v < poly.count - 1; v++) { | ||
| 352 | Vector3 v0 = poly.verts[0]; | ||
| 353 | Vector3 v1 = poly.verts[v+1]; | ||
| 354 | Vector3 v2 = poly.verts[v]; | ||
| 355 | array_push(group->vertices, v0.x); array_push(group->vertices, v0.y); array_push(group->vertices, v0.z); | ||
| 356 | array_push(group->vertices, v1.x); array_push(group->vertices, v1.y); array_push(group->vertices, v1.z); | ||
| 357 | array_push(group->vertices, v2.x); array_push(group->vertices, v2.y); array_push(group->vertices, v2.z); | ||
| 358 | Vector2 uv0 = GetUV(v0, mp, plane); | ||
| 359 | Vector2 uv1 = GetUV(v1, mp, plane); | ||
| 360 | Vector2 uv2 = GetUV(v2, mp, plane); | ||
| 361 | array_push(group->texcoords, uv0.x); array_push(group->texcoords, uv0.y); | ||
| 362 | array_push(group->texcoords, uv1.x); array_push(group->texcoords, uv1.y); | ||
| 363 | array_push(group->texcoords, uv2.x); array_push(group->texcoords, uv2.y); | ||
| 364 | Vector3 out_normal = Vector3Scale(plane.normal, -1.0f); | ||
| 365 | array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); | ||
| 366 | array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); | ||
| 367 | array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); | ||
| 368 | } | ||
| 369 | } | ||
| 370 | PolyFree(poly); | ||
| 371 | } | ||
| 372 | } | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | array(Model) final_models; | ||
| 377 | array_init(final_models); | ||
| 378 | |||
| 379 | for (int i = 0; i < groups.length; i++) { | ||
| 380 | TextureGroup *g = &groups.data[i]; | ||
| 381 | if (g->vertices.length == 0) continue; | ||
| 382 | Mesh mesh = { 0 }; | ||
| 383 | mesh.vertexCount = (int)g->vertices.length / 3; | ||
| 384 | mesh.triangleCount = mesh.vertexCount / 3; | ||
| 385 | mesh.vertices = (float *)malloc(g->vertices.length * sizeof(float)); | ||
| 386 | memcpy(mesh.vertices, g->vertices.data, g->vertices.length * sizeof(float)); | ||
| 387 | mesh.texcoords = (float *)malloc(g->texcoords.length * sizeof(float)); | ||
| 388 | memcpy(mesh.texcoords, g->texcoords.data, g->texcoords.length * sizeof(float)); | ||
| 389 | mesh.normals = (float *)malloc(g->normals.length * sizeof(float)); | ||
| 390 | memcpy(mesh.normals, g->normals.data, g->normals.length * sizeof(float)); | ||
| 391 | UploadMesh(&mesh, false); | ||
| 392 | Model model = LoadModelFromMesh(mesh); | ||
| 393 | model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = GetTexture(g->texture); | ||
| 394 | array_push(final_models, model); | ||
| 395 | array_free(g->vertices); | ||
| 396 | array_free(g->texcoords); | ||
| 397 | array_free(g->normals); | ||
| 398 | } | ||
| 399 | |||
| 400 | game.map.models = final_models.data; | ||
| 401 | game.map.count = (int)final_models.length; | ||
| 402 | array_free(groups); | ||
| 403 | |||
| 404 | FreeMap(map); | ||
| 405 | TraceLog(LOG_INFO, "Processed %d brushes into %d models", total_brushes, game.map.count); | ||
| 406 | return true; | ||
| 407 | } | ||
| 408 | |||
| 409 | bool CheckMapCollision(Vector3 start, Vector3 end, RayCollision *outCollision) { | ||
| 410 | Vector3 diff = Vector3Subtract(end, start); | ||
| 411 | float maxDist = Vector3Length(diff); | ||
| 412 | if (maxDist < 0.001f) return false; | ||
| 413 | |||
| 414 | Ray ray = { 0 }; | ||
| 415 | ray.position = start; | ||
| 416 | ray.direction = Vector3Scale(diff, 1.0f / maxDist); | ||
| 417 | |||
| 418 | RayCollision closest = { 0 }; | ||
| 419 | closest.distance = FLT_MAX; | ||
| 420 | closest.hit = false; | ||
| 421 | |||
| 422 | for (int i = 0; i < game.map.count; i++) { | ||
| 423 | Model model = game.map.models[i]; | ||
| 424 | for (int m = 0; m < model.meshCount; m++) { | ||
| 425 | RayCollision col = GetRayCollisionMesh(ray, model.meshes[m], model.transform); | ||
| 426 | if (col.hit && col.distance < closest.distance && col.distance <= maxDist) { | ||
| 427 | closest = col; | ||
| 428 | } | ||
| 429 | } | ||
| 430 | } | ||
| 431 | |||
| 432 | if (closest.hit) { | ||
| 433 | if (outCollision) *outCollision = closest; | ||
| 434 | return true; | ||
| 435 | } | ||
| 436 | return false; | ||
| 246 | } | 437 | } |
diff --git a/maps/demo1.map b/maps/demo1.map index f7f73f7..a4e2168 100644 --- a/maps/demo1.map +++ b/maps/demo1.map | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | // brush 0 | 6 | // brush 0 |
| 7 | { | 7 | { |
| 8 | ( -176 -64 -16 ) ( -176 -63 -16 ) ( -176 -64 -15 ) brushes/ground_068 0 0 0 1 1 | 8 | ( -176 -64 -16 ) ( -176 -63 -16 ) ( -176 -64 -15 ) brushes/ground_068 0 0 0 1 1 |
| 9 | ( -64 -64 -16 ) ( -64 -64 -15 ) ( -63 -64 -16 ) brushes/ground_068 0 0 0 1 1 | 9 | ( -64 -112 -16 ) ( -64 -112 -15 ) ( -63 -112 -16 ) brushes/ground_068 0 0 0 1 1 |
| 10 | ( -64 -64 -16 ) ( -63 -64 -16 ) ( -64 -63 -16 ) brushes/ground_068 0 0 0 1 1 | 10 | ( -64 -64 -16 ) ( -63 -64 -16 ) ( -64 -63 -16 ) brushes/ground_068 0 0 0 1 1 |
| 11 | ( 64 64 16 ) ( 64 65 16 ) ( 65 64 16 ) brushes/ground_068 0 0 0 1 1 | 11 | ( 64 64 16 ) ( 64 65 16 ) ( 65 64 16 ) brushes/ground_068 0 0 0 1 1 |
| 12 | ( 64 64 16 ) ( 65 64 16 ) ( 64 64 17 ) brushes/ground_068 0 0 0 1 1 | 12 | ( 64 64 16 ) ( 65 64 16 ) ( 64 64 17 ) brushes/ground_068 0 0 0 1 1 |
| @@ -15,7 +15,7 @@ | |||
| 15 | // brush 1 | 15 | // brush 1 |
| 16 | { | 16 | { |
| 17 | ( 64 176 16 ) ( 64 -80 16 ) ( 64 -80 -16 ) brushes/bricks_076c 16 -16 0 1 1 | 17 | ( 64 176 16 ) ( 64 -80 16 ) ( 64 -80 -16 ) brushes/bricks_076c 16 -16 0 1 1 |
| 18 | ( 64 -80 16 ) ( 304 -80 0 ) ( 304 -80 -16 ) brushes/bricks_076c 0 0 0 1 1 | 18 | ( 64 -80 16 ) ( 304 -80 0 ) ( 304 -80 -16 ) brushes/bricks_076c -21 0 0 1 1 |
| 19 | ( 304 -80 -16 ) ( 304 176 -16 ) ( 64 176 -16 ) brushes/bricks_076c 0 0 0 1 1 | 19 | ( 304 -80 -16 ) ( 304 176 -16 ) ( 64 176 -16 ) brushes/bricks_076c 0 0 0 1 1 |
| 20 | ( 304 176 -16 ) ( 304 176 0 ) ( 64 176 16 ) brushes/bricks_076c 0 0 0 1 1 | 20 | ( 304 176 -16 ) ( 304 176 0 ) ( 64 176 16 ) brushes/bricks_076c 0 0 0 1 1 |
| 21 | ( 64 176 16 ) ( 304 176 0 ) ( 304 -80 0 ) brushes/bricks_076c 0 0 0 1 1 | 21 | ( 64 176 16 ) ( 304 176 0 ) ( 304 -80 0 ) brushes/bricks_076c 0 0 0 1 1 |
diff --git a/maps/demo3.map b/maps/demo3.map new file mode 100644 index 0000000..f12f33c --- /dev/null +++ b/maps/demo3.map | |||
| @@ -0,0 +1,234 @@ | |||
| 1 | // Game: Stalag | ||
| 2 | // Format: Standard | ||
| 3 | // entity 0 | ||
| 4 | { | ||
| 5 | "classname" "worldspawn" | ||
| 6 | // brush 0 | ||
| 7 | { | ||
| 8 | ( -592 -16 -32 ) ( -592 -15 -32 ) ( -592 -16 -31 ) brushes/bricks_076c -48 -16 0 1 1 | ||
| 9 | ( -64 -384 -32 ) ( -64 -384 -31 ) ( -63 -384 -32 ) brushes/bricks_076c 0 -16 0 1 1 | ||
| 10 | ( -64 -16 -16 ) ( -63 -16 -16 ) ( -64 -15 -16 ) brushes/bricks_076c 0 48 0 1 1 | ||
| 11 | ( 64 112 0 ) ( 64 113 0 ) ( 65 112 0 ) brushes/bricks_076c 0 48 0 1 1 | ||
| 12 | ( 64 336 0 ) ( 65 336 0 ) ( 64 336 1 ) brushes/bricks_076c 0 -16 0 1 1 | ||
| 13 | ( 560 112 0 ) ( 560 112 1 ) ( 560 113 0 ) brushes/bricks_076c -48 -16 0 1 1 | ||
| 14 | } | ||
| 15 | // brush 1 | ||
| 16 | { | ||
| 17 | ( -592 320 0 ) ( -592 321 0 ) ( -592 320 1 ) environment/planks_012 0 0 0 1 1 | ||
| 18 | ( -592 320 0 ) ( -592 320 1 ) ( -591 320 0 ) environment/planks_012 0 0 0 1 1 | ||
| 19 | ( -592 320 0 ) ( -591 320 0 ) ( -592 321 0 ) environment/planks_012 0 0 0 1 1 | ||
| 20 | ( 560 336 272 ) ( 560 337 272 ) ( 561 336 272 ) environment/planks_012 0 0 0 1 1 | ||
| 21 | ( 560 336 16 ) ( 561 336 16 ) ( 560 336 17 ) environment/planks_012 0 0 0 1 1 | ||
| 22 | ( 560 336 16 ) ( 560 336 17 ) ( 560 337 16 ) environment/planks_012 0 0 0 1 1 | ||
| 23 | } | ||
| 24 | // brush 2 | ||
| 25 | { | ||
| 26 | ( -592 -384 0 ) ( -592 -383 0 ) ( -592 -384 1 ) environment/planks_012 0 0 0 1 1 | ||
| 27 | ( -592 -384 0 ) ( -592 -384 1 ) ( -591 -384 0 ) environment/planks_012 0 0 0 1 1 | ||
| 28 | ( -592 -384 0 ) ( -591 -384 0 ) ( -592 -383 0 ) environment/planks_012 0 0 0 1 1 | ||
| 29 | ( 560 -368 272 ) ( 560 -367 272 ) ( 561 -368 272 ) environment/planks_012 0 0 0 1 1 | ||
| 30 | ( 560 -368 16 ) ( 561 -368 16 ) ( 560 -368 17 ) environment/planks_012 0 0 0 1 1 | ||
| 31 | ( 560 -368 16 ) ( 560 -368 17 ) ( 560 -367 16 ) environment/planks_012 0 0 0 1 1 | ||
| 32 | } | ||
| 33 | // brush 3 | ||
| 34 | { | ||
| 35 | ( -592 -368 0 ) ( -592 -367 0 ) ( -592 -368 1 ) environment/planks_012 0 0 0 1 1 | ||
| 36 | ( -592 -368 0 ) ( -592 -368 1 ) ( -591 -368 0 ) environment/planks_012 0 0 0 1 1 | ||
| 37 | ( -592 -368 0 ) ( -591 -368 0 ) ( -592 -367 0 ) environment/planks_012 0 0 0 1 1 | ||
| 38 | ( -576 320 272 ) ( -576 321 272 ) ( -575 320 272 ) environment/planks_012 0 0 0 1 1 | ||
| 39 | ( -576 320 16 ) ( -575 320 16 ) ( -576 320 17 ) environment/planks_012 0 0 0 1 1 | ||
| 40 | ( -576 320 16 ) ( -576 320 17 ) ( -576 321 16 ) environment/planks_012 0 0 0 1 1 | ||
| 41 | } | ||
| 42 | // brush 4 | ||
| 43 | { | ||
| 44 | ( 544 -368 0 ) ( 544 -367 0 ) ( 544 -368 1 ) environment/planks_012 0 0 0 1 1 | ||
| 45 | ( 544 -368 0 ) ( 544 -368 1 ) ( 545 -368 0 ) environment/planks_012 0 0 0 1 1 | ||
| 46 | ( 544 -368 0 ) ( 545 -368 0 ) ( 544 -367 0 ) environment/planks_012 0 0 0 1 1 | ||
| 47 | ( 560 320 272 ) ( 560 321 272 ) ( 561 320 272 ) environment/planks_012 0 0 0 1 1 | ||
| 48 | ( 560 320 16 ) ( 561 320 16 ) ( 560 320 17 ) environment/planks_012 0 0 0 1 1 | ||
| 49 | ( 560 320 16 ) ( 560 320 17 ) ( 560 321 16 ) environment/planks_012 0 0 0 1 1 | ||
| 50 | } | ||
| 51 | // brush 5 | ||
| 52 | { | ||
| 53 | ( -352 -352 -8 ) ( -352 -351 -8 ) ( -352 -352 -7 ) environment/paintedwood_008a -16 -8 0 1 1 | ||
| 54 | ( -368 -352 -8 ) ( -368 -352 -7 ) ( -367 -352 -8 ) environment/paintedwood_008a 0 -8 0 1 1 | ||
| 55 | ( -368 -352 8 ) ( -367 -352 8 ) ( -368 -351 8 ) environment/paintedwood_008a 0 16 0 1 1 | ||
| 56 | ( -240 -144 24 ) ( -240 -143 24 ) ( -239 -144 24 ) environment/paintedwood_008a 0 16 0 1 1 | ||
| 57 | ( -240 -144 8 ) ( -239 -144 8 ) ( -240 -144 9 ) environment/paintedwood_008a 0 -8 0 1 1 | ||
| 58 | ( -240 -144 8 ) ( -240 -144 9 ) ( -240 -143 8 ) environment/paintedwood_008a -16 -8 0 1 1 | ||
| 59 | } | ||
| 60 | // brush 6 | ||
| 61 | { | ||
| 62 | ( -256 -168 0 ) ( -256 -167 0 ) ( -256 -168 1 ) environment/planks_012 -24 0 0 1 1 | ||
| 63 | ( -256 -168 0 ) ( -256 -168 1 ) ( -255 -168 0 ) environment/planks_012 0 0 0 1 1 | ||
| 64 | ( -256 -168 0 ) ( -255 -168 0 ) ( -256 -167 0 ) environment/planks_012 0 24 0 1 1 | ||
| 65 | ( -240 -152 8 ) ( -240 -151 8 ) ( -239 -152 8 ) environment/planks_012 0 24 0 1 1 | ||
| 66 | ( -240 -152 16 ) ( -239 -152 16 ) ( -240 -152 17 ) environment/planks_012 0 0 0 1 1 | ||
| 67 | ( -240 -152 16 ) ( -240 -152 17 ) ( -240 -151 16 ) environment/planks_012 -24 0 0 1 1 | ||
| 68 | } | ||
| 69 | // brush 7 | ||
| 70 | { | ||
| 71 | ( -352 -168 0 ) ( -352 -167 0 ) ( -352 -168 1 ) environment/planks_012 -24 0 0 1 1 | ||
| 72 | ( -352 -168 0 ) ( -352 -168 1 ) ( -351 -168 0 ) environment/planks_012 96 0 0 1 1 | ||
| 73 | ( -352 -168 0 ) ( -351 -168 0 ) ( -352 -167 0 ) environment/planks_012 96 24 0 1 1 | ||
| 74 | ( -336 -152 8 ) ( -336 -151 8 ) ( -335 -152 8 ) environment/planks_012 96 24 0 1 1 | ||
| 75 | ( -336 -152 16 ) ( -335 -152 16 ) ( -336 -152 17 ) environment/planks_012 96 0 0 1 1 | ||
| 76 | ( -336 -152 16 ) ( -336 -152 17 ) ( -336 -151 16 ) environment/planks_012 -24 0 0 1 1 | ||
| 77 | } | ||
| 78 | // brush 8 | ||
| 79 | { | ||
| 80 | ( -352 -344 0 ) ( -352 -343 0 ) ( -352 -344 1 ) environment/planks_012 152 0 0 1 1 | ||
| 81 | ( -352 -344 0 ) ( -352 -344 1 ) ( -351 -344 0 ) environment/planks_012 96 0 0 1 1 | ||
| 82 | ( -352 -344 0 ) ( -351 -344 0 ) ( -352 -343 0 ) environment/planks_012 96 -152 0 1 1 | ||
| 83 | ( -336 -328 8 ) ( -336 -327 8 ) ( -335 -328 8 ) environment/planks_012 96 -152 0 1 1 | ||
| 84 | ( -336 -328 16 ) ( -335 -328 16 ) ( -336 -328 17 ) environment/planks_012 96 0 0 1 1 | ||
| 85 | ( -336 -328 16 ) ( -336 -328 17 ) ( -336 -327 16 ) environment/planks_012 152 0 0 1 1 | ||
| 86 | } | ||
| 87 | // brush 9 | ||
| 88 | { | ||
| 89 | ( -256 -344 0 ) ( -256 -343 0 ) ( -256 -344 1 ) environment/planks_012 152 0 0 1 1 | ||
| 90 | ( -256 -344 0 ) ( -256 -344 1 ) ( -255 -344 0 ) environment/planks_012 0 0 0 1 1 | ||
| 91 | ( -256 -344 0 ) ( -255 -344 0 ) ( -256 -343 0 ) environment/planks_012 0 -152 0 1 1 | ||
| 92 | ( -240 -328 8 ) ( -240 -327 8 ) ( -239 -328 8 ) environment/planks_012 0 -152 0 1 1 | ||
| 93 | ( -240 -328 16 ) ( -239 -328 16 ) ( -240 -328 17 ) environment/planks_012 0 0 0 1 1 | ||
| 94 | ( -240 -328 16 ) ( -240 -328 17 ) ( -240 -327 16 ) environment/planks_012 152 0 0 1 1 | ||
| 95 | } | ||
| 96 | // brush 10 | ||
| 97 | { | ||
| 98 | ( -328 -328 32 ) ( -344 -344 16 ) ( -344 -280 16 ) environment/roofingtiles_012b 8 -344 0 1 1 | ||
| 99 | ( -272 -328 40 ) ( -256 -344 24 ) ( -352 -344 24 ) environment/roofingtiles_012b 208 -376 0 1 1 | ||
| 100 | ( -256 -344 24 ) ( -256 -280 24 ) ( -352 -280 24 ) environment/roofingtiles_012b 208 24 0 1 1 | ||
| 101 | ( -336 -296 32 ) ( -272 -296 32 ) ( -272 -328 32 ) environment/roofingtiles_012b 208 24 0 1 1 | ||
| 102 | ( -272 -296 40 ) ( -336 -296 40 ) ( -352 -280 24 ) environment/roofingtiles_012b 208 -312 0 1 1 | ||
| 103 | ( -272 -296 40 ) ( -256 -280 24 ) ( -256 -344 24 ) environment/roofingtiles_012b -80 -344 0 1 1 | ||
| 104 | } | ||
| 105 | // brush 11 | ||
| 106 | { | ||
| 107 | ( -160 -328 32 ) ( -176 -344 16 ) ( -176 -280 16 ) environment/roofingtiles_012b -160 -344 0 1 1 | ||
| 108 | ( -104 -328 40 ) ( -88 -344 24 ) ( -184 -344 24 ) environment/roofingtiles_012b 40 -376 0 1 1 | ||
| 109 | ( -88 -344 24 ) ( -88 -280 24 ) ( -184 -280 24 ) environment/roofingtiles_012b 40 24 0 1 1 | ||
| 110 | ( -168 -296 32 ) ( -104 -296 32 ) ( -104 -328 32 ) environment/roofingtiles_012b 40 24 0 1 1 | ||
| 111 | ( -104 -296 40 ) ( -168 -296 40 ) ( -184 -280 24 ) environment/roofingtiles_012b 40 -312 0 1 1 | ||
| 112 | ( -104 -296 40 ) ( -88 -280 24 ) ( -88 -344 24 ) environment/roofingtiles_012b -248 -344 0 1 1 | ||
| 113 | } | ||
| 114 | // brush 12 | ||
| 115 | { | ||
| 116 | ( -184 -352 -8 ) ( -184 -351 -8 ) ( -184 -352 -7 ) environment/paintedwood_008a -16 -8 0 1 1 | ||
| 117 | ( -200 -352 -8 ) ( -200 -352 -7 ) ( -199 -352 -8 ) environment/paintedwood_008a -168 -8 0 1 1 | ||
| 118 | ( -200 -352 8 ) ( -199 -352 8 ) ( -200 -351 8 ) environment/paintedwood_008a -168 16 0 1 1 | ||
| 119 | ( -72 -144 24 ) ( -72 -143 24 ) ( -71 -144 24 ) environment/paintedwood_008a -168 16 0 1 1 | ||
| 120 | ( -72 -144 8 ) ( -71 -144 8 ) ( -72 -144 9 ) environment/paintedwood_008a -168 -8 0 1 1 | ||
| 121 | ( -72 -144 8 ) ( -72 -144 9 ) ( -72 -143 8 ) environment/paintedwood_008a -16 -8 0 1 1 | ||
| 122 | } | ||
| 123 | // brush 13 | ||
| 124 | { | ||
| 125 | ( 0 -328 32 ) ( -16 -344 16 ) ( -16 -280 16 ) environment/roofingtiles_012b -320 -344 0 1 1 | ||
| 126 | ( 56 -328 40 ) ( 72 -344 24 ) ( -24 -344 24 ) environment/roofingtiles_012b -120 -376 0 1 1 | ||
| 127 | ( 72 -344 24 ) ( 72 -280 24 ) ( -24 -280 24 ) environment/roofingtiles_012b -120 24 0 1 1 | ||
| 128 | ( -8 -296 32 ) ( 56 -296 32 ) ( 56 -328 32 ) environment/roofingtiles_012b -120 24 0 1 1 | ||
| 129 | ( 56 -296 40 ) ( -8 -296 40 ) ( -24 -280 24 ) environment/roofingtiles_012b -120 -312 0 1 1 | ||
| 130 | ( 56 -296 40 ) ( 72 -280 24 ) ( 72 -344 24 ) environment/roofingtiles_012b -408 -344 0 1 1 | ||
| 131 | } | ||
| 132 | // brush 14 | ||
| 133 | { | ||
| 134 | ( -24 -352 -8 ) ( -24 -351 -8 ) ( -24 -352 -7 ) environment/paintedwood_008a -16 -8 0 1 1 | ||
| 135 | ( -40 -352 -8 ) ( -40 -352 -7 ) ( -39 -352 -8 ) environment/paintedwood_008a -328 -8 0 1 1 | ||
| 136 | ( -40 -352 8 ) ( -39 -352 8 ) ( -40 -351 8 ) environment/paintedwood_008a -328 16 0 1 1 | ||
| 137 | ( 88 -144 24 ) ( 88 -143 24 ) ( 89 -144 24 ) environment/paintedwood_008a -328 16 0 1 1 | ||
| 138 | ( 88 -144 8 ) ( 89 -144 8 ) ( 88 -144 9 ) environment/paintedwood_008a -328 -8 0 1 1 | ||
| 139 | ( 88 -144 8 ) ( 88 -144 9 ) ( 88 -143 8 ) environment/paintedwood_008a -16 -8 0 1 1 | ||
| 140 | } | ||
| 141 | // brush 15 | ||
| 142 | { | ||
| 143 | ( -184 -344 0 ) ( -184 -343 0 ) ( -184 -344 1 ) environment/planks_012 152 0 0 1 1 | ||
| 144 | ( -184 -344 0 ) ( -184 -344 1 ) ( -183 -344 0 ) environment/planks_012 -72 0 0 1 1 | ||
| 145 | ( -184 -344 0 ) ( -183 -344 0 ) ( -184 -343 0 ) environment/planks_012 -72 -152 0 1 1 | ||
| 146 | ( -168 -328 8 ) ( -168 -327 8 ) ( -167 -328 8 ) environment/planks_012 -72 -152 0 1 1 | ||
| 147 | ( -168 -328 16 ) ( -167 -328 16 ) ( -168 -328 17 ) environment/planks_012 -72 0 0 1 1 | ||
| 148 | ( -168 -328 16 ) ( -168 -328 17 ) ( -168 -327 16 ) environment/planks_012 152 0 0 1 1 | ||
| 149 | } | ||
| 150 | // brush 16 | ||
| 151 | { | ||
| 152 | ( -88 -344 0 ) ( -88 -343 0 ) ( -88 -344 1 ) environment/planks_012 152 0 0 1 1 | ||
| 153 | ( -88 -344 0 ) ( -88 -344 1 ) ( -87 -344 0 ) environment/planks_012 -168 0 0 1 1 | ||
| 154 | ( -88 -344 0 ) ( -87 -344 0 ) ( -88 -343 0 ) environment/planks_012 -168 -152 0 1 1 | ||
| 155 | ( -72 -328 8 ) ( -72 -327 8 ) ( -71 -328 8 ) environment/planks_012 -168 -152 0 1 1 | ||
| 156 | ( -72 -328 16 ) ( -71 -328 16 ) ( -72 -328 17 ) environment/planks_012 -168 0 0 1 1 | ||
| 157 | ( -72 -328 16 ) ( -72 -328 17 ) ( -72 -327 16 ) environment/planks_012 152 0 0 1 1 | ||
| 158 | } | ||
| 159 | // brush 17 | ||
| 160 | { | ||
| 161 | ( -24 -344 0 ) ( -24 -343 0 ) ( -24 -344 1 ) environment/planks_012 152 0 0 1 1 | ||
| 162 | ( -24 -344 0 ) ( -24 -344 1 ) ( -23 -344 0 ) environment/planks_012 -232 0 0 1 1 | ||
| 163 | ( -24 -344 0 ) ( -23 -344 0 ) ( -24 -343 0 ) environment/planks_012 -232 -152 0 1 1 | ||
| 164 | ( -8 -328 8 ) ( -8 -327 8 ) ( -7 -328 8 ) environment/planks_012 -232 -152 0 1 1 | ||
| 165 | ( -8 -328 16 ) ( -7 -328 16 ) ( -8 -328 17 ) environment/planks_012 -232 0 0 1 1 | ||
| 166 | ( -8 -328 16 ) ( -8 -328 17 ) ( -8 -327 16 ) environment/planks_012 152 0 0 1 1 | ||
| 167 | } | ||
| 168 | // brush 18 | ||
| 169 | { | ||
| 170 | ( 72 -344 0 ) ( 72 -343 0 ) ( 72 -344 1 ) environment/planks_012 152 0 0 1 1 | ||
| 171 | ( 72 -344 0 ) ( 72 -344 1 ) ( 73 -344 0 ) environment/planks_012 -328 0 0 1 1 | ||
| 172 | ( 72 -344 0 ) ( 73 -344 0 ) ( 72 -343 0 ) environment/planks_012 -328 -152 0 1 1 | ||
| 173 | ( 88 -328 8 ) ( 88 -327 8 ) ( 89 -328 8 ) environment/planks_012 -328 -152 0 1 1 | ||
| 174 | ( 88 -328 16 ) ( 89 -328 16 ) ( 88 -328 17 ) environment/planks_012 -328 0 0 1 1 | ||
| 175 | ( 88 -328 16 ) ( 88 -328 17 ) ( 88 -327 16 ) environment/planks_012 152 0 0 1 1 | ||
| 176 | } | ||
| 177 | // brush 19 | ||
| 178 | { | ||
| 179 | ( -24 -168 0 ) ( -24 -167 0 ) ( -24 -168 1 ) environment/planks_012 -24 0 0 1 1 | ||
| 180 | ( -24 -168 0 ) ( -24 -168 1 ) ( -23 -168 0 ) environment/planks_012 -232 0 0 1 1 | ||
| 181 | ( -24 -168 0 ) ( -23 -168 0 ) ( -24 -167 0 ) environment/planks_012 -232 24 0 1 1 | ||
| 182 | ( -8 -152 8 ) ( -8 -151 8 ) ( -7 -152 8 ) environment/planks_012 -232 24 0 1 1 | ||
| 183 | ( -8 -152 16 ) ( -7 -152 16 ) ( -8 -152 17 ) environment/planks_012 -232 0 0 1 1 | ||
| 184 | ( -8 -152 16 ) ( -8 -152 17 ) ( -8 -151 16 ) environment/planks_012 -24 0 0 1 1 | ||
| 185 | } | ||
| 186 | // brush 20 | ||
| 187 | { | ||
| 188 | ( 72 -168 0 ) ( 72 -167 0 ) ( 72 -168 1 ) environment/planks_012 -24 0 0 1 1 | ||
| 189 | ( 72 -168 0 ) ( 72 -168 1 ) ( 73 -168 0 ) environment/planks_012 -328 0 0 1 1 | ||
| 190 | ( 72 -168 0 ) ( 73 -168 0 ) ( 72 -167 0 ) environment/planks_012 -328 24 0 1 1 | ||
| 191 | ( 88 -152 8 ) ( 88 -151 8 ) ( 89 -152 8 ) environment/planks_012 -328 24 0 1 1 | ||
| 192 | ( 88 -152 16 ) ( 89 -152 16 ) ( 88 -152 17 ) environment/planks_012 -328 0 0 1 1 | ||
| 193 | ( 88 -152 16 ) ( 88 -152 17 ) ( 88 -151 16 ) environment/planks_012 -24 0 0 1 1 | ||
| 194 | } | ||
| 195 | // brush 21 | ||
| 196 | { | ||
| 197 | ( -184 -168 0 ) ( -184 -167 0 ) ( -184 -168 1 ) environment/planks_012 -24 0 0 1 1 | ||
| 198 | ( -184 -168 0 ) ( -184 -168 1 ) ( -183 -168 0 ) environment/planks_012 -72 0 0 1 1 | ||
| 199 | ( -184 -168 0 ) ( -183 -168 0 ) ( -184 -167 0 ) environment/planks_012 -72 24 0 1 1 | ||
| 200 | ( -168 -152 8 ) ( -168 -151 8 ) ( -167 -152 8 ) environment/planks_012 -72 24 0 1 1 | ||
| 201 | ( -168 -152 16 ) ( -167 -152 16 ) ( -168 -152 17 ) environment/planks_012 -72 0 0 1 1 | ||
| 202 | ( -168 -152 16 ) ( -168 -152 17 ) ( -168 -151 16 ) environment/planks_012 -24 0 0 1 1 | ||
| 203 | } | ||
| 204 | // brush 22 | ||
| 205 | { | ||
| 206 | ( -88 -168 0 ) ( -88 -167 0 ) ( -88 -168 1 ) environment/planks_012 -24 0 0 1 1 | ||
| 207 | ( -88 -168 0 ) ( -88 -168 1 ) ( -87 -168 0 ) environment/planks_012 -168 0 0 1 1 | ||
| 208 | ( -88 -168 0 ) ( -87 -168 0 ) ( -88 -167 0 ) environment/planks_012 -168 24 0 1 1 | ||
| 209 | ( -72 -152 8 ) ( -72 -151 8 ) ( -71 -152 8 ) environment/planks_012 -168 24 0 1 1 | ||
| 210 | ( -72 -152 16 ) ( -71 -152 16 ) ( -72 -152 17 ) environment/planks_012 -168 0 0 1 1 | ||
| 211 | ( -72 -152 16 ) ( -72 -152 17 ) ( -72 -151 16 ) environment/planks_012 -24 0 0 1 1 | ||
| 212 | } | ||
| 213 | } | ||
| 214 | // entity 1 | ||
| 215 | { | ||
| 216 | "classname" "light" | ||
| 217 | "origin" "152 -280 232" | ||
| 218 | } | ||
| 219 | // entity 2 | ||
| 220 | { | ||
| 221 | "classname" "light" | ||
| 222 | "origin" "-312 24 232" | ||
| 223 | } | ||
| 224 | // entity 3 | ||
| 225 | { | ||
| 226 | "classname" "info_player_start" | ||
| 227 | "origin" "-464 0 16" | ||
| 228 | } | ||
| 229 | // entity 4 | ||
| 230 | { | ||
| 231 | "classname" "my_entity" | ||
| 232 | "origin" "8 -72 8" | ||
| 233 | "speed" "100" | ||
| 234 | } | ||
| @@ -0,0 +1,39 @@ | |||
| 1 | #include "all.h" | ||
| 2 | |||
| 3 | void UpdateMenu(void) { | ||
| 4 | if (IsKeyPressed(KEY_ENTER)) { | ||
| 5 | if (LoadMap(game.map_path)) { | ||
| 6 | game.mode = STATE_PLAYING; | ||
| 7 | game.cursor_captured = true; | ||
| 8 | DisableCursor(); | ||
| 9 | } | ||
| 10 | } | ||
| 11 | } | ||
| 12 | |||
| 13 | void DrawMenu(void) { | ||
| 14 | BeginDrawing(); | ||
| 15 | ClearBackground(BLACK); | ||
| 16 | |||
| 17 | int screenWidth = GetScreenWidth(); | ||
| 18 | int screenHeight = GetScreenHeight(); | ||
| 19 | |||
| 20 | const char *title = "STALAG"; | ||
| 21 | const char *sub = "Press ENTER to Start"; | ||
| 22 | |||
| 23 | int titleSize = 60; | ||
| 24 | int subSize = 20; | ||
| 25 | |||
| 26 | Vector2 titlePos = { | ||
| 27 | (float)(screenWidth - MeasureTextEx(game.font_ui, title, (float)titleSize, 4).x) / 2, | ||
| 28 | (float)screenHeight / 2 - 40 | ||
| 29 | }; | ||
| 30 | Vector2 subPos = { | ||
| 31 | (float)(screenWidth - MeasureTextEx(game.font_ui, sub, (float)subSize, 2).x) / 2, | ||
| 32 | (float)screenHeight / 2 + 40 | ||
| 33 | }; | ||
| 34 | |||
| 35 | DrawTextEx(game.font_ui, title, titlePos, (float)titleSize, 4, WHITE); | ||
| 36 | DrawTextEx(game.font_ui, sub, subPos, (float)subSize, 2, GRAY); | ||
| 37 | |||
| 38 | EndDrawing(); | ||
| 39 | } | ||
| @@ -1,55 +1,24 @@ | |||
| 1 | #include "all.h" | 1 | #include "all.h" |
| 2 | #include "raymath.h" | 2 | #include "raymath.h" |
| 3 | |||
| 3 | #include <float.h> | 4 | #include <float.h> |
| 4 | 5 | ||
| 5 | static void PlayerRotate(void) { | 6 | static void PlayerRotate(void) { |
| 6 | if (!game.cursor_captured) return; | 7 | if (!game.cursor_captured) return; |
| 7 | 8 | ||
| 8 | Vector2 mouseDelta = GetMouseDelta(); | 9 | Vector2 mouseDelta = GetMouseDelta(); |
| 9 | game.yaw += -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY; | 10 | game.player.yaw += -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY; |
| 10 | game.pitch += mouseDelta.y * PLAYER_MOUSE_SENSITIVITY; | 11 | game.player.pitch += mouseDelta.y * PLAYER_MOUSE_SENSITIVITY; |
| 11 | 12 | ||
| 12 | // Clamp pitch to avoid gimbal lock/flipping (approx 89 degrees) | 13 | if (game.player.pitch > 89.0f * DEG2RAD) game.player.pitch = 89.0f * DEG2RAD; |
| 13 | if (game.pitch > 89.0f * DEG2RAD) game.pitch = 89.0f * DEG2RAD; | 14 | if (game.player.pitch < -89.0f * DEG2RAD) game.player.pitch = -89.0f * DEG2RAD; |
| 14 | if (game.pitch < -89.0f * DEG2RAD) game.pitch = -89.0f * DEG2RAD; | ||
| 15 | } | ||
| 16 | |||
| 17 | static bool CheckWorldCollision(Vector3 start, Vector3 end, RayCollision *outCollision) { | ||
| 18 | Vector3 diff = Vector3Subtract(end, start); | ||
| 19 | float maxDist = Vector3Length(diff); | ||
| 20 | if (maxDist < 0.001f) return false; | ||
| 21 | |||
| 22 | Ray ray = { 0 }; | ||
| 23 | ray.position = start; | ||
| 24 | ray.direction = Vector3Scale(diff, 1.0f / maxDist); | ||
| 25 | |||
| 26 | RayCollision closest = { 0 }; | ||
| 27 | closest.distance = FLT_MAX; | ||
| 28 | closest.hit = false; | ||
| 29 | |||
| 30 | for (int i = 0; i < game.world_model_count; i++) { | ||
| 31 | Model model = game.world_models[i]; | ||
| 32 | for (int m = 0; m < model.meshCount; m++) { | ||
| 33 | RayCollision col = GetRayCollisionMesh(ray, model.meshes[m], model.transform); | ||
| 34 | if (col.hit && col.distance < closest.distance && col.distance <= maxDist) { | ||
| 35 | closest = col; | ||
| 36 | } | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | if (closest.hit) { | ||
| 41 | if (outCollision) *outCollision = closest; | ||
| 42 | return true; | ||
| 43 | } | ||
| 44 | return false; | ||
| 45 | } | 15 | } |
| 46 | 16 | ||
| 47 | static void MoveNormal(float dt) { | 17 | static void MoveNormal(float dt) { |
| 48 | float eyeHeight = PLAYER_EYE_HEIGHT - (game.crouch_amount * PLAYER_CROUCH_OFFSET); | 18 | float eyeHeight = PLAYER_EYE_HEIGHT - (game.player.crouch_amount * PLAYER_CROUCH_OFFSET); |
| 49 | 19 | ||
| 50 | // Movement vectors based on yaw | 20 | Vector3 forward = { sinf(game.player.yaw), 0, cosf(game.player.yaw) }; |
| 51 | Vector3 forward = { sinf(game.yaw), 0, cosf(game.yaw) }; | 21 | Vector3 right = { sinf(game.player.yaw - PI/2.0f), 0, cosf(game.player.yaw - PI/2.0f) }; |
| 52 | Vector3 right = { sinf(game.yaw - PI/2.0f), 0, cosf(game.yaw - PI/2.0f) }; | ||
| 53 | 22 | ||
| 54 | Vector3 moveDir = { 0 }; | 23 | Vector3 moveDir = { 0 }; |
| 55 | if (IsKeyDown(KEY_W)) moveDir = Vector3Add(moveDir, forward); | 24 | if (IsKeyDown(KEY_W)) moveDir = Vector3Add(moveDir, forward); |
| @@ -59,24 +28,19 @@ static void MoveNormal(float dt) { | |||
| 59 | 28 | ||
| 60 | float speed = PLAYER_MOVE_SPEED; | 29 | float speed = PLAYER_MOVE_SPEED; |
| 61 | if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) speed *= PLAYER_SPRINT_MULTIPLIER; | 30 | if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) speed *= PLAYER_SPRINT_MULTIPLIER; |
| 62 | 31 | speed *= Lerp(1.0f, PLAYER_CROUCH_MOVE_MULTIPLIER, game.player.crouch_amount); | |
| 63 | // Apply crouch speed penalty | ||
| 64 | speed *= Lerp(1.0f, PLAYER_CROUCH_MOVE_MULTIPLIER, game.crouch_amount); | ||
| 65 | 32 | ||
| 66 | if (Vector3Length(moveDir) > 0) { | 33 | if (Vector3Length(moveDir) > 0) { |
| 67 | moveDir = Vector3Scale(Vector3Normalize(moveDir), speed * dt); | 34 | moveDir = Vector3Scale(Vector3Normalize(moveDir), speed * dt); |
| 68 | } | 35 | } |
| 69 | 36 | ||
| 70 | // Apply gravity | 37 | game.player.velocity.y -= PLAYER_GRAVITY * dt; |
| 71 | game.velocity.y -= PLAYER_GRAVITY * dt; | ||
| 72 | 38 | ||
| 73 | // Jump | 39 | if (game.player.is_grounded && IsKeyPressed(KEY_SPACE)) { |
| 74 | if (game.is_grounded && IsKeyPressed(KEY_SPACE)) { | 40 | game.player.velocity.y = PLAYER_JUMP_FORCE; |
| 75 | game.velocity.y = PLAYER_JUMP_FORCE; | 41 | game.player.is_grounded = false; |
| 76 | game.is_grounded = false; | ||
| 77 | } | 42 | } |
| 78 | 43 | ||
| 79 | // Horizontal movement with sliding collision | ||
| 80 | Vector3 remainingMove = moveDir; | 44 | Vector3 remainingMove = moveDir; |
| 81 | for (int iter = 0; iter < 4 && Vector3Length(remainingMove) > 0.001f; iter++) { | 45 | for (int iter = 0; iter < 4 && Vector3Length(remainingMove) > 0.001f; iter++) { |
| 82 | RayCollision closestHit = { 0 }; | 46 | RayCollision closestHit = { 0 }; |
| @@ -84,7 +48,7 @@ static void MoveNormal(float dt) { | |||
| 84 | bool hitFound = false; | 48 | bool hitFound = false; |
| 85 | 49 | ||
| 86 | float heights[] = { -eyeHeight + 10.0f, -eyeHeight / 2.0f, 0.0f }; | 50 | float heights[] = { -eyeHeight + 10.0f, -eyeHeight / 2.0f, 0.0f }; |
| 87 | Vector3 currentPos = game.pos; | 51 | Vector3 currentPos = game.player.pos; |
| 88 | 52 | ||
| 89 | for (int i = 0; i < 3; i++) { | 53 | for (int i = 0; i < 3; i++) { |
| 90 | Vector3 start = Vector3Add(currentPos, (Vector3){0, heights[i], 0}); | 54 | Vector3 start = Vector3Add(currentPos, (Vector3){0, heights[i], 0}); |
| @@ -93,8 +57,7 @@ static void MoveNormal(float dt) { | |||
| 93 | Vector3 end = Vector3Add(start, Vector3Scale(dir, dist)); | 57 | Vector3 end = Vector3Add(start, Vector3Scale(dir, dist)); |
| 94 | 58 | ||
| 95 | RayCollision col; | 59 | RayCollision col; |
| 96 | if (CheckWorldCollision(start, end, &col)) { | 60 | if (CheckMapCollision(start, end, &col)) { |
| 97 | // Adjust distance to be relative to the player boundary | ||
| 98 | float adjustedDist = col.distance - PLAYER_RADIUS; | 61 | float adjustedDist = col.distance - PLAYER_RADIUS; |
| 99 | if (adjustedDist < closestHit.distance) { | 62 | if (adjustedDist < closestHit.distance) { |
| 100 | closestHit = col; | 63 | closestHit = col; |
| @@ -105,13 +68,11 @@ static void MoveNormal(float dt) { | |||
| 105 | } | 68 | } |
| 106 | 69 | ||
| 107 | if (hitFound) { | 70 | if (hitFound) { |
| 108 | // Move as far as possible | ||
| 109 | float moveDist = fmaxf(0, closestHit.distance - 0.1f); | 71 | float moveDist = fmaxf(0, closestHit.distance - 0.1f); |
| 110 | Vector3 moveStep = Vector3Scale(Vector3Normalize(remainingMove), moveDist); | 72 | Vector3 moveStep = Vector3Scale(Vector3Normalize(remainingMove), moveDist); |
| 111 | game.pos.x += moveStep.x; | 73 | game.player.pos.x += moveStep.x; |
| 112 | game.pos.z += moveStep.z; | 74 | game.player.pos.z += moveStep.z; |
| 113 | 75 | ||
| 114 | // Project remaining movement onto the plane of the wall | ||
| 115 | Vector3 slideNormal = { closestHit.normal.x, 0, closestHit.normal.z }; | 76 | Vector3 slideNormal = { closestHit.normal.x, 0, closestHit.normal.z }; |
| 116 | if (Vector3Length(slideNormal) > 0.001f) { | 77 | if (Vector3Length(slideNormal) > 0.001f) { |
| 117 | slideNormal = Vector3Normalize(slideNormal); | 78 | slideNormal = Vector3Normalize(slideNormal); |
| @@ -122,68 +83,61 @@ static void MoveNormal(float dt) { | |||
| 122 | remainingMove = (Vector3){0, 0, 0}; | 83 | remainingMove = (Vector3){0, 0, 0}; |
| 123 | } | 84 | } |
| 124 | } else { | 85 | } else { |
| 125 | // No collision found, move the rest of the way | 86 | game.player.pos.x += remainingMove.x; |
| 126 | game.pos.x += remainingMove.x; | 87 | game.player.pos.z += remainingMove.z; |
| 127 | game.pos.z += remainingMove.z; | ||
| 128 | break; | 88 | break; |
| 129 | } | 89 | } |
| 130 | } | 90 | } |
| 131 | 91 | ||
| 132 | // Vertical movement with collision | 92 | float verticalMove = game.player.velocity.y * dt; |
| 133 | float verticalMove = game.velocity.y * dt; | 93 | Vector3 vStart = game.player.pos; |
| 134 | Vector3 vStart = game.pos; | ||
| 135 | 94 | ||
| 136 | if (verticalMove < 0) { // Falling/Down | 95 | if (verticalMove < 0) { |
| 137 | Vector3 start = vStart; | 96 | Vector3 start = vStart; |
| 138 | // Check slightly below feet | ||
| 139 | Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove - eyeHeight, 0}); | 97 | Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove - eyeHeight, 0}); |
| 140 | RayCollision vCol; | 98 | RayCollision vCol; |
| 141 | if (CheckWorldCollision(start, end, &vCol) && vCol.normal.y > 0.5f) { | 99 | if (CheckMapCollision(start, end, &vCol) && vCol.normal.y > 0.5f) { |
| 142 | game.pos.y = vCol.point.y + eyeHeight; | 100 | game.player.pos.y = vCol.point.y + eyeHeight; |
| 143 | game.velocity.y = 0; | 101 | game.player.velocity.y = 0; |
| 144 | game.is_grounded = true; | 102 | game.player.is_grounded = true; |
| 145 | } else { | 103 | } else { |
| 146 | game.pos.y += verticalMove; | 104 | game.player.pos.y += verticalMove; |
| 147 | game.is_grounded = false; | 105 | game.player.is_grounded = false; |
| 148 | } | 106 | } |
| 149 | } else if (verticalMove > 0) { // Jumping/Up | 107 | } else if (verticalMove > 0) { |
| 150 | Vector3 start = vStart; | 108 | Vector3 start = vStart; |
| 151 | // Check above head | ||
| 152 | float headHeight = PLAYER_HEIGHT - PLAYER_EYE_HEIGHT; | 109 | float headHeight = PLAYER_HEIGHT - PLAYER_EYE_HEIGHT; |
| 153 | Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove + headHeight, 0}); | 110 | Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove + headHeight, 0}); |
| 154 | RayCollision vCol; | 111 | RayCollision vCol; |
| 155 | if (CheckWorldCollision(start, end, &vCol)) { | 112 | if (CheckMapCollision(start, end, &vCol)) { |
| 156 | game.pos.y = vCol.point.y - headHeight - 1.0f; | 113 | game.player.pos.y = vCol.point.y - headHeight - 1.0f; |
| 157 | game.velocity.y = 0; | 114 | game.player.velocity.y = 0; |
| 158 | } else { | 115 | } else { |
| 159 | game.pos.y += verticalMove; | 116 | game.player.pos.y += verticalMove; |
| 160 | } | 117 | } |
| 161 | game.is_grounded = false; | 118 | game.player.is_grounded = false; |
| 162 | } else { | 119 | } else { |
| 163 | // Not moving vertically, but check if we are still on ground | ||
| 164 | Vector3 start = vStart; | 120 | Vector3 start = vStart; |
| 165 | Vector3 end = Vector3Add(vStart, (Vector3){0, -eyeHeight - 2.0f, 0}); | 121 | Vector3 end = Vector3Add(vStart, (Vector3){0, -eyeHeight - 2.0f, 0}); |
| 166 | RayCollision vCol; | 122 | RayCollision vCol; |
| 167 | if (CheckWorldCollision(start, end, &vCol) && vCol.normal.y > 0.5f) { | 123 | if (CheckMapCollision(start, end, &vCol) && vCol.normal.y > 0.5f) { |
| 168 | game.is_grounded = true; | 124 | game.player.is_grounded = true; |
| 169 | // Snap to floor if very close | ||
| 170 | if (vCol.distance < eyeHeight + 1.0f) { | 125 | if (vCol.distance < eyeHeight + 1.0f) { |
| 171 | game.pos.y = vCol.point.y + eyeHeight; | 126 | game.player.pos.y = vCol.point.y + eyeHeight; |
| 172 | } | 127 | } |
| 173 | } else { | 128 | } else { |
| 174 | game.is_grounded = false; | 129 | game.player.is_grounded = false; |
| 175 | } | 130 | } |
| 176 | } | 131 | } |
| 177 | } | 132 | } |
| 178 | 133 | ||
| 179 | static void MoveFly(float dt) { | 134 | static void MoveFly(float dt) { |
| 180 | // Full 3D movement based on yaw/pitch | ||
| 181 | Vector3 forward = { | 135 | Vector3 forward = { |
| 182 | cosf(game.pitch) * sinf(game.yaw), | 136 | cosf(game.player.pitch) * sinf(game.player.yaw), |
| 183 | -sinf(game.pitch), | 137 | -sinf(game.player.pitch), |
| 184 | cosf(game.pitch) * cosf(game.yaw) | 138 | cosf(game.player.pitch) * cosf(game.player.yaw) |
| 185 | }; | 139 | }; |
| 186 | Vector3 right = { sinf(game.yaw - PI/2.0f), 0, cosf(game.yaw - PI/2.0f) }; | 140 | Vector3 right = { sinf(game.player.yaw - PI/2.0f), 0, cosf(game.player.yaw - PI/2.0f) }; |
| 187 | 141 | ||
| 188 | Vector3 move = { 0 }; | 142 | Vector3 move = { 0 }; |
| 189 | if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward); | 143 | if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward); |
| @@ -191,7 +145,6 @@ static void MoveFly(float dt) { | |||
| 191 | if (IsKeyDown(KEY_D)) move = Vector3Add(move, right); | 145 | if (IsKeyDown(KEY_D)) move = Vector3Add(move, right); |
| 192 | if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right); | 146 | if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right); |
| 193 | 147 | ||
| 194 | // Fly up/down | ||
| 195 | if (IsKeyDown(KEY_SPACE)) move.y += 1.0f; | 148 | if (IsKeyDown(KEY_SPACE)) move.y += 1.0f; |
| 196 | if (IsKeyDown(KEY_LEFT_CONTROL)) move.y -= 1.0f; | 149 | if (IsKeyDown(KEY_LEFT_CONTROL)) move.y -= 1.0f; |
| 197 | 150 | ||
| @@ -200,86 +153,75 @@ static void MoveFly(float dt) { | |||
| 200 | 153 | ||
| 201 | if (Vector3Length(move) > 0) { | 154 | if (Vector3Length(move) > 0) { |
| 202 | move = Vector3Scale(Vector3Normalize(move), speed * dt); | 155 | move = Vector3Scale(Vector3Normalize(move), speed * dt); |
| 203 | game.pos = Vector3Add(game.pos, move); | 156 | game.player.pos = Vector3Add(game.player.pos, move); |
| 204 | } | 157 | } |
| 205 | 158 | ||
| 206 | game.velocity = (Vector3){0, 0, 0}; | 159 | game.player.velocity = (Vector3){0, 0, 0}; |
| 207 | game.is_grounded = false; | 160 | game.player.is_grounded = false; |
| 208 | } | 161 | } |
| 209 | 162 | ||
| 210 | void UpdatePlayer(void) { | 163 | void UpdatePlayer(void) { |
| 211 | float dt = GetFrameTime(); | 164 | float dt = GetFrameTime(); |
| 212 | Vector3 oldPos = game.pos; | 165 | Vector3 oldPos = game.player.pos; |
| 213 | 166 | ||
| 214 | if (IsKeyPressed(KEY_F)) { | 167 | if (IsKeyPressed(KEY_F)) { |
| 215 | game.move_mode = (game.move_mode == MOVE_NORMAL) ? MOVE_FLY : MOVE_NORMAL; | 168 | game.player.move_mode = (game.player.move_mode == MOVE_NORMAL) ? MOVE_FLY : MOVE_NORMAL; |
| 216 | TraceLog(LOG_INFO, "Movement mode: %s", (game.move_mode == MOVE_NORMAL) ? "NORMAL" : "FLY"); | ||
| 217 | } | 169 | } |
| 218 | 170 | ||
| 219 | PlayerRotate(); | 171 | PlayerRotate(); |
| 220 | 172 | ||
| 221 | // Crouching logic | ||
| 222 | bool canStand = true; | 173 | bool canStand = true; |
| 223 | if (game.crouch_amount > 0.1f) { | 174 | if (game.player.crouch_amount > 0.1f) { |
| 224 | Vector3 headPos = game.pos; | 175 | Vector3 headPos = game.player.pos; |
| 225 | Vector3 headEnd = Vector3Add(headPos, (Vector3){0, PLAYER_HEIGHT - PLAYER_EYE_HEIGHT + PLAYER_CROUCH_OFFSET, 0}); | 176 | Vector3 headEnd = Vector3Add(headPos, (Vector3){0, PLAYER_HEIGHT - PLAYER_EYE_HEIGHT + PLAYER_CROUCH_OFFSET, 0}); |
| 226 | RayCollision col; | 177 | RayCollision col; |
| 227 | if (CheckWorldCollision(headPos, headEnd, &col)) { | 178 | if (CheckMapCollision(headPos, headEnd, &col)) { |
| 228 | canStand = false; | 179 | canStand = false; |
| 229 | } | 180 | } |
| 230 | } | 181 | } |
| 231 | 182 | ||
| 232 | float targetCrouch = 0.0f; | 183 | float targetCrouch = 0.0f; |
| 233 | if (IsKeyDown(KEY_LEFT_CONTROL) || !canStand) targetCrouch = 1.0f; | 184 | if (IsKeyDown(KEY_LEFT_CONTROL) || !canStand) targetCrouch = 1.0f; |
| 234 | game.crouch_amount = Lerp(game.crouch_amount, targetCrouch, PLAYER_CROUCH_SPEED * dt); | 185 | game.player.crouch_amount = Lerp(game.player.crouch_amount, targetCrouch, PLAYER_CROUCH_SPEED * dt); |
| 235 | 186 | ||
| 236 | // Leaning logic | ||
| 237 | float targetLean = 0.0f; | 187 | float targetLean = 0.0f; |
| 238 | if (IsKeyDown(KEY_Q)) targetLean -= 1.0f; | 188 | if (IsKeyDown(KEY_Q)) targetLean -= 1.0f; |
| 239 | if (IsKeyDown(KEY_E)) targetLean += 1.0f; | 189 | if (IsKeyDown(KEY_E)) targetLean += 1.0f; |
| 190 | game.player.lean_amount = Lerp(game.player.lean_amount, targetLean, PLAYER_LEAN_SPEED * dt); | ||
| 240 | 191 | ||
| 241 | game.lean_amount = Lerp(game.lean_amount, targetLean, PLAYER_LEAN_SPEED * dt); | 192 | if (game.player.move_mode == MOVE_FLY) { |
| 242 | |||
| 243 | if (game.move_mode == MOVE_FLY) { | ||
| 244 | MoveFly(dt); | 193 | MoveFly(dt); |
| 245 | } else { | 194 | } else { |
| 246 | MoveNormal(dt); | 195 | MoveNormal(dt); |
| 247 | } | 196 | } |
| 248 | 197 | ||
| 249 | // Apply lean as a pivot from the neck/waist | 198 | float leanAngle = game.player.lean_amount * PLAYER_LEAN_ANGLE * DEG2RAD; |
| 250 | float leanAngle = game.lean_amount * PLAYER_LEAN_ANGLE * DEG2RAD; | 199 | Vector3 bodyForward = { sinf(game.player.yaw), 0, cosf(game.player.yaw) }; |
| 251 | Vector3 bodyForward = { sinf(game.yaw), 0, cosf(game.yaw) }; | ||
| 252 | 200 | ||
| 253 | float currentEyeHeight = PLAYER_EYE_HEIGHT - (game.crouch_amount * PLAYER_CROUCH_OFFSET); | 201 | float currentEyeHeight = PLAYER_EYE_HEIGHT - (game.player.crouch_amount * PLAYER_CROUCH_OFFSET); |
| 254 | float pivotDist = fminf(PLAYER_LEAN_PIVOT_DISTANCE, currentEyeHeight * 0.8f); | 202 | float pivotDist = fminf(PLAYER_LEAN_PIVOT_DISTANCE, currentEyeHeight * 0.8f); |
| 255 | 203 | ||
| 256 | Vector3 neckToEye = { 0, pivotDist, 0 }; | 204 | Vector3 neckToEye = { 0, pivotDist, 0 }; |
| 257 | Vector3 rotatedOffset = Vector3RotateByAxisAngle(neckToEye, bodyForward, leanAngle); | 205 | Vector3 rotatedOffset = Vector3RotateByAxisAngle(neckToEye, bodyForward, leanAngle); |
| 258 | Vector3 pivot = Vector3Subtract(game.pos, (Vector3){ 0, pivotDist, 0 }); | 206 | Vector3 pivot = Vector3Subtract(game.player.pos, (Vector3){ 0, pivotDist, 0 }); |
| 259 | 207 | ||
| 260 | // Update camera based on physical pos + visual lean pivot | ||
| 261 | game.camera.position = Vector3Add(pivot, rotatedOffset); | 208 | game.camera.position = Vector3Add(pivot, rotatedOffset); |
| 262 | 209 | ||
| 263 | // Apply roll to up vector | ||
| 264 | Vector3 forward = { | 210 | Vector3 forward = { |
| 265 | cosf(game.pitch) * sinf(game.yaw), | 211 | cosf(game.player.pitch) * sinf(game.player.yaw), |
| 266 | -sinf(game.pitch), | 212 | -sinf(game.player.pitch), |
| 267 | cosf(game.pitch) * cosf(game.yaw) | 213 | cosf(game.player.pitch) * cosf(game.player.yaw) |
| 268 | }; | 214 | }; |
| 269 | 215 | ||
| 270 | // Use a stable world-up vector and apply lean roll | ||
| 271 | Vector3 up = { 0, 1, 0 }; | 216 | Vector3 up = { 0, 1, 0 }; |
| 272 | up = Vector3RotateByAxisAngle(up, forward, leanAngle); | 217 | up = Vector3RotateByAxisAngle(up, forward, leanAngle); |
| 273 | game.camera.up = up; | 218 | game.camera.up = up; |
| 274 | |||
| 275 | // Use a longer target distance for better precision in the projection matrix | ||
| 276 | game.camera.target = Vector3Add(game.camera.position, Vector3Scale(forward, 20.0f)); | 219 | game.camera.target = Vector3Add(game.camera.position, Vector3Scale(forward, 20.0f)); |
| 277 | 220 | ||
| 278 | // Calculate horizontal speed for UI | ||
| 279 | if (dt > 0) { | 221 | if (dt > 0) { |
| 280 | Vector2 velH = { game.pos.x - oldPos.x, game.pos.z - oldPos.z }; | 222 | Vector2 velH = { game.player.pos.x - oldPos.x, game.player.pos.z - oldPos.z }; |
| 281 | game.horizontal_speed = Vector2Length(velH) / dt; | 223 | game.player.horizontal_speed = Vector2Length(velH) / dt; |
| 282 | } else { | 224 | } else { |
| 283 | game.horizontal_speed = 0; | 225 | game.player.horizontal_speed = 0; |
| 284 | } | 226 | } |
| 285 | } | 227 | } |
diff --git a/tbrun.sh b/tbrun.sh new file mode 100755 index 0000000..d933b6d --- /dev/null +++ b/tbrun.sh | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | SCRIPT="$0" | ||
| 4 | case "$SCRIPT" in | ||
| 5 | /*) ;; | ||
| 6 | *) SCRIPT="$(pwd)/$SCRIPT" ;; | ||
| 7 | esac | ||
| 8 | |||
| 9 | # resolve symlinks | ||
| 10 | while [ -L "$SCRIPT" ]; do | ||
| 11 | LINK="$(readlink "$SCRIPT")" | ||
| 12 | case "$LINK" in | ||
| 13 | /*) SCRIPT="$LINK" ;; | ||
| 14 | *) SCRIPT="$(dirname "$SCRIPT")/$LINK" ;; | ||
| 15 | esac | ||
| 16 | done | ||
| 17 | |||
| 18 | SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd -P)" | ||
| 19 | echo "$SCRIPT_DIR" | ||
| 20 | |||
| 21 | MAP="" | ||
| 22 | while [ $# -gt 0 ]; do | ||
| 23 | case "$1" in | ||
| 24 | --map) | ||
| 25 | shift | ||
| 26 | MAP="$1" | ||
| 27 | ;; | ||
| 28 | --map=*) | ||
| 29 | MAP="${1#--map=}" | ||
| 30 | ;; | ||
| 31 | --) shift; break;; | ||
| 32 | *) ;; | ||
| 33 | esac | ||
| 34 | shift | ||
| 35 | done | ||
| 36 | |||
| 37 | cd "$SCRIPT_DIR" | ||
| 38 | ./bin/stalag --map "$MAP" | ||
diff --git a/trenchbroom/stalag/Entities.fgd b/trenchbroom/stalag/Entities.fgd new file mode 100644 index 0000000..abf5256 --- /dev/null +++ b/trenchbroom/stalag/Entities.fgd | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | @BaseClass = Targetname | ||
| 2 | [ | ||
| 3 | targetname(string) : "Name" | ||
| 4 | ] | ||
| 5 | |||
| 6 | @BaseClass = Origin | ||
| 7 | [ | ||
| 8 | origin(string) : "Origin" | ||
| 9 | ] | ||
| 10 | |||
| 11 | @SolidClass = worldspawn : "Worldspawn" | ||
| 12 | [ | ||
| 13 | message(string) : "Map title" | ||
| 14 | ] | ||
| 15 | |||
| 16 | @PointClass base(Targetname, Origin) size(-16 -16 -16, 16 16 16) = info_player_start : "Player Start" | ||
| 17 | [ | ||
| 18 | angle(integer) : "Angle" : 0 | ||
| 19 | ] | ||
| 20 | |||
| 21 | @PointClass base(Targetname, Origin) size(-8 -8 -8, 8 8 8) = light : "Light" | ||
| 22 | [ | ||
| 23 | light(integer) : "Brightness" : 300 | ||
| 24 | ] | ||
| 25 | |||
| 26 | @PointClass base(Targetname) = my_entity : "My Entity" | ||
| 27 | [ | ||
| 28 | speed(integer) : "Speed" : 100 | ||
| 29 | ] | ||
diff --git a/trenchbroom/stalag/GameConfig.cfg b/trenchbroom/stalag/GameConfig.cfg new file mode 100644 index 0000000..f061b4a --- /dev/null +++ b/trenchbroom/stalag/GameConfig.cfg | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | { | ||
| 2 | "version": 9, | ||
| 3 | "name": "Stalag", | ||
| 4 | |||
| 5 | "fileformats": [ | ||
| 6 | { "format": "Standard" } | ||
| 7 | ], | ||
| 8 | |||
| 9 | "filesystem": { | ||
| 10 | "searchpath": ".", | ||
| 11 | "packageformat": { "extension": ".pak", "format": "idpak" } | ||
| 12 | }, | ||
| 13 | |||
| 14 | "materials": { | ||
| 15 | "root": "textures", | ||
| 16 | "extensions": [ ".png", ".jpg", ".tga" ] | ||
| 17 | }, | ||
| 18 | |||
| 19 | "entities": { | ||
| 20 | "definitions": [ "Entities.fgd" ], | ||
| 21 | "defaultcolor": "0.6 0.6 0.6 1.0" | ||
| 22 | }, | ||
| 23 | |||
| 24 | "softMapBounds": "-4096 -4096 -4096 4096 4096 4096" | ||
| 25 | } \ No newline at end of file | ||
diff --git a/trenchbroom/stalag/GameEngineProfiles.cfg b/trenchbroom/stalag/GameEngineProfiles.cfg new file mode 100644 index 0000000..41cc5fb --- /dev/null +++ b/trenchbroom/stalag/GameEngineProfiles.cfg | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | { | ||
| 2 | "profiles": [ | ||
| 3 | { | ||
| 4 | "name": "Stalag", | ||
| 5 | "parameters": "--map maps/${MAP_BASE_NAME}.map", | ||
| 6 | "path": "/home/m/Projects/stalag/tbrun.sh" | ||
| 7 | } | ||
| 8 | ], | ||
| 9 | "version": 1 | ||
| 10 | } | ||
