diff --git a/game/client b/game/client deleted file mode 100755 index 0c15c78..0000000 Binary files a/game/client and /dev/null differ diff --git a/game/client.c b/game/client.c index 316bb36..c7eb9e5 100644 --- a/game/client.c +++ b/game/client.c @@ -21,7 +21,7 @@ typedef int socklen_t; #define CLOSESOCK close #endif -#define PROTOCOL_VERSION 67 +#define PROTOCOL_VERSION 5 #define SERVER_PORT 27015 #define MAX_PLAYERS 16 #define USERNAME_MAX 16 @@ -43,6 +43,7 @@ typedef int socklen_t; #define BTN_USE_MEDKIT (1u << 4) #define BTN_JUMP (1u << 5) +// ============== DIRECTIONAL LIGHT ============== typedef struct { Vector3 direction; // Normalized light direction Vector3 color; // RGB color (0-1 range) @@ -52,38 +53,7 @@ typedef struct { float shadowIntensity; // How dark shadows are (0-1) } DirectionalLight; -typedef struct { - Shader shader; - int locViewPos; - int locLightDir; - int locLightColor; - int locLightIntensity; - int locAmbientIntensity; - int locTerrainColor; -} TerrainShader; - -static TerrainShader load_terrain_shader(void) { - TerrainShader ts = {0}; - ts.shader = LoadShader("terrain.vs", "terrain.fs"); - - // Get uniform locations - ts.locViewPos = GetShaderLocation(ts.shader, "viewPos"); - ts.locLightDir = GetShaderLocation(ts.shader, "lightDir"); - ts.locLightColor = GetShaderLocation(ts.shader, "lightColor"); - ts.locLightIntensity = GetShaderLocation(ts.shader, "lightIntensity"); - ts.locAmbientIntensity = GetShaderLocation(ts.shader, "ambientIntensity"); - ts.locTerrainColor = GetShaderLocation(ts.shader, "terrainColor"); - - return ts; -} - -static void unload_terrain_shader(TerrainShader *ts) { - if (ts && ts->shader.id != 0) { - UnloadShader(ts->shader); - ts->shader.id = 0; - } -} - +// ============== SIMPLEX NOISE ============== static const uint8_t perm[512] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, @@ -173,13 +143,13 @@ static float fbm_noise(float x, float y, int octaves) { } float get_terrain_height(float x, float z) { - float height = fbm_noise(x * 0.05f, z * 0.05f, 2); + float height = fbm_noise(x * 0.03f, z * 0.03f, 4); height += fbm_noise(x * 0.01f, z * 0.01f, 2) * 1.5f; - return height * 4.0f + 2.0f; + return height * 6.0f + 2.0f; } -#define TERRAIN_SIZE 256 -#define TERRAIN_SCALE 1.0f +#define TERRAIN_SIZE 64 +#define TERRAIN_SCALE 2.0f static Mesh generate_terrain_mesh(void) { int size = TERRAIN_SIZE; @@ -235,53 +205,69 @@ static Mesh generate_terrain_mesh(void) { } } - // Calculate normals using tangent plane approximation from terrain gradients - // This preserves terrain curvature better than simple triangle averaging - for (int z = 0; z < size; z++) { - for (int x = 0; x < size; x++) { - int idx = z * size + x; - float wx = ((float)x - size / 2.0f) * TERRAIN_SCALE; - float wz = ((float)z - size / 2.0f) * TERRAIN_SCALE; + // Calculate normals + for (int i = 0; i < vertexCount; i++) { + mesh.normals[i * 3 + 0] = 0; + mesh.normals[i * 3 + 1] = 1; + mesh.normals[i * 3 + 2] = 0; + } - // Sample height gradients to compute terrain normal - // Use neighboring vertices for finite difference approximation - float h_right = (x + 1 < size) - ? mesh.vertices[(z * size + (x + 1)) * 3 + 1] - : mesh.vertices[idx * 3 + 1]; - float h_left = (x - 1 >= 0) ? mesh.vertices[(z * size + (x - 1)) * 3 + 1] - : mesh.vertices[idx * 3 + 1]; - float h_down = (z + 1 < size) - ? mesh.vertices[((z + 1) * size + x) * 3 + 1] - : mesh.vertices[idx * 3 + 1]; - float h_up = (z - 1 >= 0) ? mesh.vertices[((z - 1) * size + x) * 3 + 1] - : mesh.vertices[idx * 3 + 1]; + for (int t = 0; t < triangleCount; t++) { + int i0 = mesh.indices[t * 3 + 0]; + int i1 = mesh.indices[t * 3 + 1]; + int i2 = mesh.indices[t * 3 + 2]; - // Compute finite differences - float dh_dx = (h_right - h_left) / (2.0f * TERRAIN_SCALE); - float dh_dz = (h_down - h_up) / (2.0f * TERRAIN_SCALE); + float v0x = mesh.vertices[i0 * 3 + 0]; + float v0y = mesh.vertices[i0 * 3 + 1]; + float v0z = mesh.vertices[i0 * 3 + 2]; - // Normal from height field: (-dh/dx, 1, -dh/dz) then normalized - float nx = -dh_dx; - float ny = 1.0f; - float nz = -dh_dz; + float v1x = mesh.vertices[i1 * 3 + 0]; + float v1y = mesh.vertices[i1 * 3 + 1]; + float v1z = mesh.vertices[i1 * 3 + 2]; - float len = sqrtf(nx * nx + ny * ny + nz * nz); - if (len > 0.0001f) { - mesh.normals[idx * 3 + 0] = nx / len; - mesh.normals[idx * 3 + 1] = ny / len; - mesh.normals[idx * 3 + 2] = nz / len; - } else { - mesh.normals[idx * 3 + 0] = 0; - mesh.normals[idx * 3 + 1] = 1; - mesh.normals[idx * 3 + 2] = 0; - } + float v2x = mesh.vertices[i2 * 3 + 0]; + float v2y = mesh.vertices[i2 * 3 + 1]; + float v2z = mesh.vertices[i2 * 3 + 2]; + + float e1x = v1x - v0x, e1y = v1y - v0y, e1z = v1z - v0z; + float e2x = v2x - v0x, e2y = v2y - v0y, e2z = v2z - v0z; + + float nx = e1y * e2z - e1z * e2y; + float ny = e1z * e2x - e1x * e2z; + float nz = e1x * e2y - e1y * e2x; + + mesh.normals[i0 * 3 + 0] += nx; + mesh.normals[i0 * 3 + 1] += ny; + mesh.normals[i0 * 3 + 2] += nz; + + mesh.normals[i1 * 3 + 0] += nx; + mesh.normals[i1 * 3 + 1] += ny; + mesh.normals[i1 * 3 + 2] += nz; + + mesh.normals[i2 * 3 + 0] += nx; + mesh.normals[i2 * 3 + 1] += ny; + mesh.normals[i2 * 3 + 2] += nz; + } + + // Normalize normals + for (int i = 0; i < vertexCount; i++) { + float nx = mesh.normals[i * 3 + 0]; + float ny = mesh.normals[i * 3 + 1]; + float nz = mesh.normals[i * 3 + 2]; + float len = sqrtf(nx * nx + ny * ny + nz * nz); + if (len > 0.0001f) { + mesh.normals[i * 3 + 0] = nx / len; + mesh.normals[i * 3 + 1] = ny / len; + mesh.normals[i * 3 + 2] = nz / len; } } UploadMesh(&mesh, false); return mesh; } +// ============== END SIMPLEX NOISE ============== +// ============== LIGHTING HELPERS ============== // apply_directional_lighting: Basic Lambertian diffuse lighting // Applies directional light with diffuse component based on surface normal static Color apply_directional_lighting(Color baseColor, Vector3 normal, @@ -383,117 +369,7 @@ static Vector3 v3_normalize(Vector3 v) { return (Vector3){0, 1, 0}; return (Vector3){v.x / len, v.y / len, v.z / len}; } - -static Vector3 v3(float x, float y, float z) { - Vector3 v = {x, y, z}; - return v; -} -static Vector3 v3_add(Vector3 a, Vector3 b) { - return v3(a.x + b.x, a.y + b.y, a.z + b.z); -} -static Vector3 v3_sub(Vector3 a, Vector3 b) { - return v3(a.x - b.x, a.y - b.y, a.z - b.z); -} -static float v3_dot(Vector3 a, Vector3 b) { - return a.x * b.x + a.y * b.y + a.z * b.z; -} -static float v3_len(Vector3 a) { - return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z); -} -static Vector3 v3_norm(Vector3 a) { - float l = v3_len(a); - if (l <= 1e-6f) - return v3(0, 0, 1); - return v3(a.x / l, a.y / l, a.z / l); -} - -// calculate_ambient_occlusion: Estimates how occluded a point is based on -// terrain curvature Samples heights in cardinal directions and computes horizon -// angle to terrain Returns occlusion factor from 0 (fully occluded) to 1 (fully -// lit) -static float calculate_ambient_occlusion(Vector3 worldPos, Vector3 normal) { - // AO based on terrain curvature: check if terrain rises around the point - float sampleRadius = 3.0f; - float aoAccum = 0.0f; - int numSamples = 8; - - float centerHeight = worldPos.y; - - // Sample 8 directions around the point - for (int i = 0; i < numSamples; i++) { - float angle = (2.0f * 3.14159265f * (float)i) / (float)numSamples; - float sx = worldPos.x + cosf(angle) * sampleRadius; - float sz = worldPos.z + sinf(angle) * sampleRadius; - float sh = get_terrain_height(sx, sz); - - // Check if terrain is higher relative to surface normal - // Higher terrain in shadow-casting areas reduces occlusion - float heightDiff = sh - centerHeight; - if (heightDiff > 0.1f) { - // Terrain is higher, contributes to shadow - float aoAmount = fminf(1.0f, heightDiff / 2.0f); - aoAccum += aoAmount; - } - } - - float aoFactor = - 1.0f - (aoAccum / (float)numSamples) * 0.6f; // 60% max occlusion - return fmaxf(0.2f, aoFactor); // Min 20% brightness -} - -// apply_phong_lighting_per_pixel: Advanced Phong lighting with per-pixel -// normals Includes diffuse, specular highlight, and ambient occlusion for -// geometry detail -static Color apply_phong_lighting_per_pixel(Color baseColor, Vector3 normal, - Vector3 worldPos, Vector3 camPos, - DirectionalLight light) { - // Normalize inputs - Vector3 lightDir = v3_normalize(light.direction); - Vector3 normal_norm = v3_normalize(normal); - - // Compute view direction (from surface to camera) - Vector3 viewDir = v3_normalize(v3_sub(camPos, worldPos)); - - // Diffuse component: Lambertian shading - float diffuse = fmaxf(0.0f, -v3_dot(lightDir, normal_norm)); - - // Specular component: Blinn-Phong specular highlight - Vector3 halfVec = v3_normalize(v3_add( - v3_norm((Vector3){-lightDir.x, -lightDir.y, -lightDir.z}), viewDir)); - float specular = - powf(fmaxf(0.0f, v3_dot(halfVec, normal_norm)), 32.0f) * 0.5f; - - // Ambient occlusion from terrain geometry - float ao = calculate_ambient_occlusion(worldPos, normal_norm); - - // Shadow based on height (distant higher terrain casts softer shadows) - float shadowFactor = - calculate_shadow_factor(worldPos, light.direction, 10.0f); - - // Combine lighting components - float brightness = light.ambientIntensity * ao; - brightness += diffuse * light.intensity * shadowFactor * - (1.0f - light.ambientIntensity) * ao; - brightness += specular * light.intensity * shadowFactor * - 0.6f; // Specular less affected by AO - - brightness = fminf(1.0f, brightness); - - // Apply brightness to base color - int r = (int)(baseColor.r * brightness); - int g = (int)(baseColor.g * brightness); - int b = (int)(baseColor.b * brightness); - - // Clamp to valid RGB range - if (r > 255) - r = 255; - if (g > 255) - g = 255; - if (b > 255) - b = 255; - - return (Color){r, g, b, baseColor.a}; -} +// ============== END LIGHTING HELPERS ============== static void set_nonblocking(int sock) { #ifdef _WIN32 @@ -512,8 +388,7 @@ typedef enum MsgType : uint8_t { MSG_INPUT = 3, MSG_SNAPSHOT = 4, MSG_SHOOT = 5, - MSG_ITEMS = 6, - MSG_ROOM_STATE = 7 + MSG_ITEMS = 6 } MsgType; typedef struct MsgHello { @@ -548,7 +423,6 @@ typedef struct PlayerStateNet { float x, y, z, yaw, pitch; uint8_t weapon; int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; - int16_t reloadTimeLeft; char username[USERNAME_MAX]; } PlayerStateNet; @@ -572,14 +446,6 @@ typedef struct MsgItems { uint8_t count; ItemNet items[MAX_ITEMS]; } MsgItems; - -typedef struct MsgRoomState { - uint8_t type; - uint8_t state; - float countdownRemaining; - uint8_t winnerId; - char winnerName[USERNAME_MAX]; -} MsgRoomState; #pragma pack(pop) typedef struct RemotePlayer { @@ -588,7 +454,6 @@ typedef struct RemotePlayer { float yaw, pitch; uint8_t weapon; int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; - int16_t reloadTimeLeft; char username[USERNAME_MAX]; } RemotePlayer; @@ -600,6 +465,29 @@ typedef struct WorldItem { Vector3 pos; } WorldItem; +static Vector3 v3(float x, float y, float z) { + Vector3 v = {x, y, z}; + return v; +} +static Vector3 v3_add(Vector3 a, Vector3 b) { + return v3(a.x + b.x, a.y + b.y, a.z + b.z); +} +static Vector3 v3_sub(Vector3 a, Vector3 b) { + return v3(a.x - b.x, a.y - b.y, a.z - b.z); +} +static float v3_dot(Vector3 a, Vector3 b) { + return a.x * b.x + a.y * b.y + a.z * b.z; +} +static float v3_len(Vector3 a) { + return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z); +} +static Vector3 v3_norm(Vector3 a) { + float l = v3_len(a); + if (l <= 1e-6f) + return v3(0, 0, 1); + return v3(a.x / l, a.y / l, a.z / l); +} + static const char *ItemName(uint8_t t) { switch (t) { case ITEM_MEDKIT: @@ -677,20 +565,6 @@ int main(void) { dirLight.shadowBias = 0.005f; dirLight.shadowIntensity = 0.4f; - // Load terrain shader - TerrainShader terrainShader = load_terrain_shader(); - if (terrainShader.shader.id == 0) { - fprintf( - stderr, - "Warning: Failed to load terrain shader, using default rendering\n"); - // Fallback: Make terrain very bright red to show shader failed - terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = - (Color){255, 100, 100, 255}; - } else { - terrainModel.materials[0].shader = terrainShader.shader; - fprintf(stderr, "Shader loaded successfully!\n"); - } - int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock < 0) { perror("socket"); @@ -709,16 +583,10 @@ int main(void) { RemotePlayer rp[MAX_PLAYERS] = {0}; WorldItem wi[MAX_ITEMS] = {0}; - // Room state - int roomState = 0; - float countdownRemaining = 0.0f; - char winnerName[USERNAME_MAX] = {0}; - Vector3 camPos = v3(0, 5.0f, 6); float yaw = 0.0f, pitch = 0.0f; float recoilYaw = 0.0f, recoilPitch = 0.0f, crossSpread = 0.0f; - int scoped = 0; float fireCooldown = 0.0f; int shotsInBurst = 0; float burstResetTimer = 0.0f; @@ -805,7 +673,6 @@ int main(void) { p->pistolAmmo = ps->pistolAmmo; p->rifleAmmo = ps->rifleAmmo; p->medkits = ps->medkits; - p->reloadTimeLeft = ps->reloadTimeLeft; memset(p->username, 0, USERNAME_MAX); strncpy(p->username, ps->username, USERNAME_MAX - 1); } @@ -815,10 +682,6 @@ int main(void) { camPos.x = body.x; camPos.y = body.y + 1.0f; camPos.z = body.z + 0.0001f; - - // Prevent camera from clipping into terrain - float terrain_h = get_terrain_height(camPos.x, camPos.z); - camPos.y = fmaxf(camPos.y, terrain_h + 1.5f); } } else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) { MsgItems *m = (MsgItems *)buf; @@ -832,14 +695,6 @@ int main(void) { wi[i].qty = m->items[i].qty; wi[i].pos = v3(m->items[i].x, m->items[i].y, m->items[i].z); } - } else if (type == MSG_ROOM_STATE && n >= (int)sizeof(MsgRoomState)) { - const MsgRoomState *rs = (const MsgRoomState *)buf; - roomState = rs->state; - countdownRemaining = rs->countdownRemaining; - memset(winnerName, 0, USERNAME_MAX); - if (rs->winnerId < 255) { - strncpy(winnerName, rs->winnerName, USERNAME_MAX - 1); - } } } @@ -882,8 +737,6 @@ int main(void) { buttons |= BTN_USE_MEDKIT; if (IsKeyPressed(KEY_SPACE)) buttons |= BTN_JUMP; - if (IsKeyPressed(KEY_Z)) - scoped = !scoped; if (myId != 255) { MsgInput in = {0}; @@ -935,7 +788,7 @@ int main(void) { cam.position = camPos; cam.target = v3_add(camPos, forward); cam.up = v3(0, 1, 0); - cam.fovy = scoped ? 30.0f : 75.0f; + cam.fovy = 75.0f; cam.projection = CAMERA_PERSPECTIVE; BeginDrawing(); @@ -943,41 +796,20 @@ int main(void) { BeginMode3D(cam); - // Set up shader uniforms for terrain rendering - if (terrainShader.shader.id != 0) { - // Convert light direction to shader format (should be pointing TO the - // light) - float lightDirArray[3] = {-dirLight.direction.x, -dirLight.direction.y, - -dirLight.direction.z}; - float lightColorArray[3] = {dirLight.color.x, dirLight.color.y, - dirLight.color.z}; - float viewPosArray[3] = {camPos.x, camPos.y, camPos.z}; - // Terrain color in 0-1 range (80, 140, 70) / 255 - float terrainColorArray[3] = {80.0f / 255.0f, 140.0f / 255.0f, - 70.0f / 255.0f}; + // Calculate terrain normal at camera position for lighting + Vector3 terrainNormal = v3(0, 1, 0); // Default up normal - // Debug mode: 0=normal lighting, 1=show normals as colors, 2=show AO only + // Draw smooth terrain with directional lighting + shadows + // For Raylib, we apply lighting by adjusting material colors + Color terrainBaseColor = (Color){80, 140, 70, 255}; + Color litTerrainColor = apply_lighting_with_shadows( + terrainBaseColor, terrainNormal, v3(0, 2, 0), dirLight); - SetShaderValue(terrainShader.shader, terrainShader.locViewPos, - viewPosArray, SHADER_UNIFORM_VEC3); - SetShaderValue(terrainShader.shader, terrainShader.locLightDir, - lightDirArray, SHADER_UNIFORM_VEC3); - SetShaderValue(terrainShader.shader, terrainShader.locLightColor, - lightColorArray, SHADER_UNIFORM_VEC3); - SetShaderValue(terrainShader.shader, terrainShader.locLightIntensity, - &dirLight.intensity, SHADER_UNIFORM_FLOAT); - SetShaderValue(terrainShader.shader, terrainShader.locAmbientIntensity, - &dirLight.ambientIntensity, SHADER_UNIFORM_FLOAT); - SetShaderValue(terrainShader.shader, terrainShader.locTerrainColor, - terrainColorArray, SHADER_UNIFORM_VEC3); - } - - // Draw terrain with shader terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = - (Color){80, 140, 70, 255}; + litTerrainColor; DrawModel(terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE); - // Items with basic lighting (no shader for now to keep it simple) + // Items with lighting and shadows for (int i = 0; i < MAX_ITEMS; i++) { if (!wi[i].present) continue; @@ -989,14 +821,13 @@ int main(void) { if (wi[i].type == ITEM_AMMO_RIFLE) ic = (Color){255, 180, 120, 255}; - // Apply basic lighting to items - Vector3 itemNormal = v3(0, 1, 0); + // Apply lighting with shadows to items Color litColor = - apply_lighting_with_shadows(ic, itemNormal, wi[i].pos, dirLight); + apply_lighting_with_shadows(ic, terrainNormal, wi[i].pos, dirLight); DrawSphere(wi[i].pos, 0.3f, litColor); } - // Players with basic lighting + // Players with lighting and shadows for (int i = 0; i < MAX_PLAYERS; i++) { if (!rp[i].present) continue; @@ -1006,10 +837,9 @@ int main(void) { if (!rp[i].alive) c = (Color){120, 120, 120, 255}; - // Apply basic lighting to players - Vector3 playerNormal = v3(0, 1, 0); + // Apply lighting with shadows to player models Color litPlayerColor = - apply_lighting_with_shadows(c, playerNormal, p, dirLight); + apply_lighting_with_shadows(c, terrainNormal, p, dirLight); DrawCapsule(v3(p.x, p.y - 0.5f, p.z), v3(p.x, p.y + 0.5f, p.z), 0.35f, 8, 8, litPlayerColor); } @@ -1070,58 +900,29 @@ int main(void) { DrawText(TextFormat("%d", currentMag), sw - 250, sh - 75, 40, RAYWHITE); DrawText(TextFormat("/ %d", reserveAmmo), sw - 140, sh - 65, 24, (Color){180, 180, 180, 255}); - if (rp[myId].reloadTimeLeft > 0) - DrawText("RELOADING...", sw - 250, sh - 30, 20, - (Color){255, 200, 80, 255}); - else if (currentMag == 0) + if (currentMag == 0) DrawText("RELOAD!", sw - 250, sh - 30, 20, (Color){255, 80, 80, 255}); } DrawFPS(sw - 90, 10); - // Room state overlay - if (roomState == 0) { // Waiting - DrawRectangle(sw / 2 - 150, sh / 2 - 50, 300, 100, (Color){0, 0, 0, 200}); - DrawText("WAITING FOR PLAYERS", sw / 2 - 120, sh / 2 - 30, 24, RAYWHITE); - DrawText("Need at least 2 players", sw / 2 - 100, sh / 2 - 5, 18, - (Color){200, 200, 200, 255}); - } else if (roomState == 1) { // Counting down - DrawRectangle(sw / 2 - 150, sh / 2 - 50, 300, 100, (Color){0, 0, 0, 200}); - DrawText("GAME STARTING SOON", sw / 2 - 120, sh / 2 - 30, 24, - (Color){255, 255, 80, 255}); - DrawText(TextFormat("%.1f seconds", countdownRemaining), sw / 2 - 60, - sh / 2 - 5, 20, RAYWHITE); - } else if (roomState == 3) { // Finished - DrawRectangle(sw / 2 - 200, sh / 2 - 50, 400, 100, (Color){0, 0, 0, 200}); - DrawText("GAME FINISHED", sw / 2 - 80, sh / 2 - 30, 28, - (Color){255, 80, 80, 255}); - if (winnerName[0]) { - DrawText(TextFormat("Winner: %s", winnerName), sw / 2 - 100, sh / 2 - 5, - 24, (Color){255, 255, 80, 255}); - } else { - DrawText("No winner", sw / 2 - 50, sh / 2 - 5, 24, RAYWHITE); - } - } - // Crosshair { int cx = sw / 2, cy = sh / 2; - int gap = scoped ? 2 : 6 + (int)crossSpread; - int len = scoped ? 5 : 10, thick = 2; + int gap = 6 + (int)crossSpread; + int len = 10, thick = 2; Color col = (Color){240, 240, 245, 220}; DrawRectangle(cx - gap - len, cy - thick / 2, len, thick, col); DrawRectangle(cx + gap, cy - thick / 2, len, thick, col); DrawRectangle(cx - thick / 2, cy - gap - len, thick, len, col); DrawRectangle(cx - thick / 2, cy + gap, thick, len, col); - DrawCircleLines(cx, cy, scoped ? 1.5f : 3.0f, - (Color){240, 240, 245, 160}); + DrawCircleLines(cx, cy, 3.0f, (Color){240, 240, 245, 160}); } EndDrawing(); } UnloadModel(terrainModel); - unload_terrain_shader(&terrainShader); CloseWindow(); CLOSESOCK(sock); #ifdef _WIN32 diff --git a/game/libfishsoup b/game/libfishsoup deleted file mode 120000 index 9b31e29..0000000 --- a/game/libfishsoup +++ /dev/null @@ -1 +0,0 @@ -../libfishsoup \ No newline at end of file diff --git a/game/server b/game/server deleted file mode 100755 index 03a0748..0000000 Binary files a/game/server and /dev/null differ diff --git a/game/server.c b/game/server.c index b3c61b9..c497376 100644 --- a/game/server.c +++ b/game/server.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include @@ -24,12 +23,12 @@ typedef int socklen_t; #define CLOSESOCK close #endif -#define PROTOCOL_VERSION 67 +#define PROTOCOL_VERSION 5 #define SERVER_PORT 27015 #define MAX_PLAYERS 16 #define USERNAME_MAX 16 -#define TICK_HZ 20 +#define TICK_HZ 60 #define DT (1.0f / (float)TICK_HZ) #define WEAPON_PISTOL 0 @@ -49,15 +48,11 @@ typedef int socklen_t; #define BTN_USE_MEDKIT (1u << 4) #define BTN_JUMP (1u << 5) -#define ROOM_STATE_WAITING 0 -#define ROOM_STATE_COUNTING_DOWN 1 -#define ROOM_STATE_RUNNING 2 -#define ROOM_STATE_FINISHED 3 - #define CAT_WORLD (1u << 0) #define CAT_PLAYER (1u << 1) #define CAT_ITEM (1u << 2) +// ============== SIMPLEX NOISE ============== static const uint8_t perm[512] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, @@ -147,13 +142,13 @@ static float fbm_noise(float x, float y, int octaves) { } float get_terrain_height(float x, float z) { - float height = fbm_noise(x * 0.05f, z * 0.05f, 2); + float height = fbm_noise(x * 0.03f, z * 0.03f, 4); height += fbm_noise(x * 0.01f, z * 0.01f, 2) * 1.5f; - return height * 4.0f + 2.0f; + return height * 6.0f + 2.0f; } -#define TERRAIN_SIZE 256 -#define TERRAIN_SCALE 1.0f +#define TERRAIN_SIZE 64 +#define TERRAIN_SCALE 2.0f static dReal *gHeightData = NULL; static dGeomID gTerrainGeom = NULL; @@ -171,6 +166,7 @@ static void generate_smooth_terrain(void) { printf("Generated smooth heightmap terrain %dx%d\n", size, size); } +// ============== END SIMPLEX NOISE ============== static double now_seconds(void) { #ifdef _WIN32 @@ -207,8 +203,7 @@ typedef enum MsgType : uint8_t { MSG_INPUT = 3, MSG_SNAPSHOT = 4, MSG_SHOOT = 5, - MSG_ITEMS = 6, - MSG_ROOM_STATE = 7 + MSG_ITEMS = 6 } MsgType; typedef struct MsgHello { @@ -243,7 +238,6 @@ typedef struct PlayerStateNet { float x, y, z, yaw, pitch; uint8_t weapon; int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; - int16_t reloadTimeLeft; char username[USERNAME_MAX]; } PlayerStateNet; @@ -267,14 +261,6 @@ typedef struct MsgItems { uint8_t count; ItemNet items[MAX_ITEMS]; } MsgItems; - -typedef struct MsgRoomState { - uint8_t type; - uint8_t state; - float countdownRemaining; - uint8_t winnerId; - char winnerName[USERNAME_MAX]; -} MsgRoomState; #pragma pack(pop) typedef struct Item { @@ -297,19 +283,10 @@ typedef struct Player { double lastShotTime; int shotsInBurst; uint32_t rng; - float reloadTimer; - int reloadWeapon; dBodyID body; dGeomID geom; } Player; -typedef struct RoomState { - int state; - double countdownEndTime; - int winnerId; - int playerCount; -} RoomState; - static int addr_equal(const struct sockaddr_in *a, const struct sockaddr_in *b) { return (a->sin_family == b->sin_family) && (a->sin_port == b->sin_port) && @@ -338,7 +315,6 @@ static dSpaceID gSpace; static dJointGroupID gContactGroup; static Item gItems[MAX_ITEMS]; static uint16_t gNextItemId = 1; -static RoomState gRoomState; static void item_destroy(Item *it) { if (it->geom) { @@ -417,25 +393,10 @@ static void ode_init_world(void) { dGeomSetCategoryBits(gTerrainGeom, CAT_WORLD); dGeomSetCollideBits(gTerrainGeom, CAT_PLAYER); - // Spawn initial random loot - for (int i = 0; i < 4; i++) { - float x = (float)(rand() % 200 - 100); - float z = (float)(rand() % 200 - 100); - uint8_t type; - int qty; - int type_idx = rand() % 3; - if (type_idx == 0) { - type = ITEM_MEDKIT; - qty = 1; - } else if (type_idx == 1) { - type = ITEM_AMMO_PISTOL; - qty = 10 + (rand() % 6); - } else { - type = ITEM_AMMO_RIFLE; - qty = 20 + (rand() % 11); - } - item_spawn(type, qty, x, z); - } + item_spawn(ITEM_MEDKIT, 1, 10.0f, 10.0f); + item_spawn(ITEM_MEDKIT, 1, -15.0f, 20.0f); + item_spawn(ITEM_AMMO_RIFLE, 30, 5.0f, -10.0f); + item_spawn(ITEM_AMMO_PISTOL, 12, -10.0f, -15.0f); } static void ode_shutdown_world(void) { @@ -457,9 +418,8 @@ static void player_ode_create(Player *p, int spawnIndex) { dBodySetLinearDamping(p->body, 0.05); dBodySetAngularDamping(p->body, 0.99); - // Random spawn position - float sx = (float)(rand() % 200 - 100); // -100 to 99 - float sz = (float)(rand() % 200 - 100); + float sx = (float)(spawnIndex * 4 - 8); + float sz = (float)(spawnIndex * 3 - 6); float sy = get_terrain_height(sx, sz) + 3.0f; dBodySetPosition(p->body, sx, sy, sz); @@ -587,14 +547,6 @@ int main(void) { WSAStartup(MAKEWORD(2, 2), &wsa); #endif - srand((unsigned)time(NULL)); - - // Initialize room state - gRoomState.state = ROOM_STATE_WAITING; - gRoomState.countdownEndTime = 0.0; - gRoomState.winnerId = -1; - gRoomState.playerCount = 0; - ode_init_world(); int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); @@ -616,7 +568,7 @@ int main(void) { } Player players[MAX_PLAYERS] = {0}; - uint32_t serverTick = 0, itemsTickDiv = 0, spawnCounter = 0; + uint32_t serverTick = 0, itemsTickDiv = 0; printf("Server listening UDP %d\n", SERVER_PORT); double nextTick = now_seconds(); @@ -654,9 +606,6 @@ int main(void) { continue; if (pid < 0) { - // Only allow joining if room is not running - if (gRoomState.state == ROOM_STATE_RUNNING) - continue; for (int i = 0; i < MAX_PLAYERS; i++) if (!players[i].inUse) { pid = i; @@ -676,7 +625,6 @@ int main(void) { p->medkits = 2; p->rng = 0xA341316Cu ^ (uint32_t)(i * 2654435761u); player_ode_create(p, i); - gRoomState.playerCount++; printf("Player %d joined as '%s'\n", pid, p->username); break; } @@ -708,9 +656,20 @@ int main(void) { p->weapon = WEAPON_RIFLE; if (m->buttons & BTN_RELOAD) { - if (p->reloadTimer <= 0.0f) { - p->reloadTimer = 2.0f; // 2 second reload time - p->reloadWeapon = p->weapon; + if (p->weapon == WEAPON_PISTOL) { + int need = 12 - p->pistolMag; + if (need > 0 && p->pistolAmmo > 0) { + int take = (p->pistolAmmo < need) ? p->pistolAmmo : need; + p->pistolAmmo -= take; + p->pistolMag += take; + } + } else { + int need = 30 - p->rifleMag; + if (need > 0 && p->rifleAmmo > 0) { + int take = (p->rifleAmmo < need) ? p->rifleAmmo : need; + p->rifleAmmo -= take; + p->rifleMag += take; + } } } @@ -753,15 +712,14 @@ int main(void) { if (m->buttons & BTN_JUMP) { const dReal *lvel = dBodyGetLinearVel(p->body); - dBodySetLinearVel(p->body, lvel[0], 12.0f, - lvel[2]); // Set upward velocity + dBodySetLinearVel(p->body, lvel[0], 12.0f, lvel[2]); // Set upward velocity printf("Player %d jumped\n", pid); } } } else if (type == MSG_SHOOT && n >= (int)sizeof(MsgShoot)) { const MsgShoot *m = (const MsgShoot *)buf; if (pid >= 0 && m->playerId == (uint8_t)pid && players[pid].inUse && - players[pid].alive && gRoomState.state == ROOM_STATE_RUNNING) { + players[pid].alive) { Player *sh = &players[pid]; const double now = now_seconds(); double fireInterval = @@ -771,9 +729,6 @@ int main(void) { if (now - sh->lastShotTime > 0.18) sh->shotsInBurst = 0; - if (sh->reloadTimer > 0.0f) - continue; - int *mag = (sh->weapon == WEAPON_PISTOL) ? &sh->pistolMag : &sh->rifleMag; if (*mag <= 0) @@ -829,37 +784,6 @@ int main(void) { if (players[victim].hp <= 0) { players[victim].hp = 0; players[victim].alive = 0; - // Drop loot - const dReal *pos = dBodyGetPosition(players[victim].body); - float x = (float)pos[0], z = (float)pos[2]; - if (players[victim].pistolAmmo > 0) { - int qty = players[victim].pistolAmmo; - if (qty > 12) - qty = 12; // Max per drop - item_spawn(ITEM_AMMO_PISTOL, qty, - x + frand_signed(&players[victim].rng) * 2.0f, - z + frand_signed(&players[victim].rng) * 2.0f); - players[victim].pistolAmmo -= qty; - } - if (players[victim].rifleAmmo > 0) { - int qty = players[victim].rifleAmmo; - if (qty > 30) - qty = 30; - item_spawn(ITEM_AMMO_RIFLE, qty, - x + frand_signed(&players[victim].rng) * 2.0f, - z + frand_signed(&players[victim].rng) * 2.0f); - players[victim].rifleAmmo -= qty; - } - if (players[victim].medkits > 0) { - int qty = players[victim].medkits; - if (qty > 2) - qty = 2; - item_spawn(ITEM_MEDKIT, qty, - x + frand_signed(&players[victim].rng) * 2.0f, - z + frand_signed(&players[victim].rng) * 2.0f); - players[victim].medkits -= qty; - } - printf("Player %d died and dropped loot\n", victim); } } } @@ -867,128 +791,15 @@ int main(void) { } } - // Room state management - { - double now = now_seconds(); - if (gRoomState.state == ROOM_STATE_WAITING) { - // Count active players - int activePlayers = 0; - for (int i = 0; i < MAX_PLAYERS; i++) { - if (players[i].inUse) - activePlayers++; - } - gRoomState.playerCount = activePlayers; - if (activePlayers >= 2) { - gRoomState.state = ROOM_STATE_COUNTING_DOWN; - gRoomState.countdownEndTime = now + 10.0; // 10 seconds - printf("Starting countdown for game start\n"); - } - } else if (gRoomState.state == ROOM_STATE_COUNTING_DOWN) { - if (now >= gRoomState.countdownEndTime) { - gRoomState.state = ROOM_STATE_RUNNING; - // Respawn and heal all players - for (int i = 0; i < MAX_PLAYERS; i++) { - if (players[i].inUse) { - players[i].alive = 1; - players[i].hp = 100; - player_ode_create(&players[i], i); // Recreate body - } - } - printf("Game started!\n"); - } - } else if (gRoomState.state == ROOM_STATE_RUNNING) { - // Check win condition - int aliveCount = 0; - int lastAlive = -1; - for (int i = 0; i < MAX_PLAYERS; i++) { - if (players[i].inUse && players[i].alive) { - aliveCount++; - lastAlive = i; - } - } - if (aliveCount <= 1) { - gRoomState.state = ROOM_STATE_FINISHED; - gRoomState.winnerId = lastAlive; - printf("Game finished! Winner: %s\n", - lastAlive >= 0 ? players[lastAlive].username : "None"); - } - } else if (gRoomState.state == ROOM_STATE_FINISHED) { - // Wait a bit, then reset - static double finishTime = 0.0; - if (finishTime == 0.0) - finishTime = now; - if (now - finishTime > 5.0) { // 5 seconds after finish - gRoomState.state = ROOM_STATE_WAITING; - gRoomState.winnerId = -1; - finishTime = 0.0; - // Remove dead players or something? For now, keep them - printf("Room resetting to waiting\n"); - } - } - } - double t = now_seconds(); if (t >= nextTick) { nextTick += DT; serverTick++; - spawnCounter++; dSpaceCollide(gSpace, 0, &nearCallback); dWorldQuickStep(gWorld, DT); dJointGroupEmpty(gContactGroup); - // Handle reload timers - for (int i = 0; i < MAX_PLAYERS; i++) { - Player *p = &players[i]; - if (!p->inUse || !p->alive) - continue; - if (p->reloadTimer > 0.0f) { - p->reloadTimer -= DT; - if (p->reloadTimer <= 0.0f) { - p->reloadTimer = 0.0f; - // Perform reload - if (p->reloadWeapon == WEAPON_PISTOL) { - int need = 12 - p->pistolMag; - if (need > 0 && p->pistolAmmo > 0) { - int take = (p->pistolAmmo < need) ? p->pistolAmmo : need; - p->pistolAmmo -= take; - p->pistolMag += take; - } - } else { - int need = 30 - p->rifleMag; - if (need > 0 && p->rifleAmmo > 0) { - int take = (p->rifleAmmo < need) ? p->rifleAmmo : need; - p->rifleAmmo -= take; - p->rifleMag += take; - } - } - } - } - } - - // Spawn random items every 30 seconds (900 ticks at 30Hz) - if (spawnCounter >= 900) { - spawnCounter = 0; - for (int i = 0; i < 2; i++) { - int type_idx = rand() % 3; - uint8_t type; - int qty; - if (type_idx == 0) { - type = ITEM_MEDKIT; - qty = 1; - } else if (type_idx == 1) { - type = ITEM_AMMO_PISTOL; - qty = 10 + (rand() % 6); - } else { - type = ITEM_AMMO_RIFLE; - qty = 20 + (rand() % 11); - } - float x = (float)(rand() % 200 - 100); - float z = (float)(rand() % 200 - 100); - item_spawn(type, qty, x, z); - } - } - for (int i = 0; i < MAX_PLAYERS; i++) { Player *p = &players[i]; if (!p->inUse || !p->alive) @@ -1004,7 +815,7 @@ int main(void) { dz /= len; } - float speed = 10.0f; + float speed = 6.0f; float fx = sinf(p->yaw) * dz - cosf(p->yaw) * dx; float fz = cosf(p->yaw) * dz + sinf(p->yaw) * dx; dBodySetLinearVel(p->body, fx * speed, lvel[1], fz * speed); @@ -1035,7 +846,6 @@ int main(void) { ps->pistolAmmo = (int16_t)players[i].pistolAmmo; ps->rifleAmmo = (int16_t)players[i].rifleAmmo; ps->medkits = (int16_t)players[i].medkits; - ps->reloadTimeLeft = (int16_t)(players[i].reloadTimer * 1000.0f); strncpy(ps->username, players[i].username, USERNAME_MAX - 1); } broadcast(sock, players, &snap, (int)sizeof(snap)); @@ -1060,27 +870,6 @@ int main(void) { } broadcast(sock, players, &im, (int)sizeof(im)); } - - // Send room state every few ticks - static uint32_t roomStateTickDiv = 0; - roomStateTickDiv++; - if (roomStateTickDiv >= 30) { // Every 30 ticks, about 0.5s - roomStateTickDiv = 0; - MsgRoomState rs = {0}; - rs.type = MSG_ROOM_STATE; - rs.state = (uint8_t)gRoomState.state; - rs.countdownRemaining = - (gRoomState.state == ROOM_STATE_COUNTING_DOWN) - ? (float)(gRoomState.countdownEndTime - now_seconds()) - : 0.0f; - rs.winnerId = - (gRoomState.winnerId >= 0) ? (uint8_t)gRoomState.winnerId : 255; - if (gRoomState.winnerId >= 0) { - strncpy(rs.winnerName, players[gRoomState.winnerId].username, - USERNAME_MAX - 1); - } - broadcast(sock, players, &rs, (int)sizeof(rs)); - } } double now = now_seconds(); diff --git a/game/terrain.fs b/game/terrain.fs deleted file mode 100644 index fae6101..0000000 --- a/game/terrain.fs +++ /dev/null @@ -1,86 +0,0 @@ -#version 330 - -// Input from vertex shader -in vec3 fragPosition; -in vec3 fragNormal; -in vec2 fragTexCoord; - -// Uniforms -uniform sampler2D texture0; -uniform vec3 viewPos; -uniform vec3 lightDir; -uniform vec3 lightColor; -uniform float lightIntensity; -uniform float ambientIntensity; -uniform vec3 terrainColor; - -// Output -out vec4 finalColor; - -// Simplex noise for AO calculation -float hash(float n) { - return fract(sin(n) * 43758.5453123); -} - -float noise(vec3 p) { - vec3 i = floor(p); - vec3 f = fract(p); - f = f * f * (3.0 - 2.0 * f); - - float n = i.x + i.y * 157.0 + i.z * 113.0; - return mix( - mix(mix(hash(n), hash(n + 1.0), f.x), - mix(hash(n + 157.0), hash(n + 158.0), f.x), f.y), - mix(mix(hash(n + 113.0), hash(n + 114.0), f.x), - mix(hash(n + 270.0), hash(n + 271.0), f.x), f.y), - f.z - ); -} - -// Calculate ambient occlusion with more contrast -float calculateAO(vec3 position, vec3 normal) { - // Sample 8 directions around the surface - float ao = 0.0; - float radius = 2.5; - int samples = 8; - - for (int i = 0; i < samples; i++) { - float angle = 6.28318530718 * float(i) / float(samples); - vec3 offset = vec3(cos(angle) * radius, noise(position * 0.5 + float(i)), sin(angle) * radius); - - // Sample height variation (approximated via noise) - float heightVariation = noise((position + offset) * 0.3); - - // Calculate occlusion based on height and normal - float dotProduct = max(0.0, dot(normal, normalize(offset))); - ao += mix(0.0, dotProduct * heightVariation, 0.7); - } - - // Increased contrast: 0.8 multiplier instead of 0.6 - ao = 1.0 - (ao / float(samples)) * 0.8; - // Min 0.1 (darker shadows) instead of 0.2 - return max(0.1, ao); -} - -void main() -{ - // Normalize interpolated normal - vec3 norm = normalize(fragNormal); - - // Use normal Y component to create geometry-revealing green shading - // Higher Y normal (upward-facing) = brighter green (peaks/flat areas) - // Lower Y normal (sloped) = darker green (slopes/valleys) - float brightness = (norm.y + 1.0) * 0.5; // Map -1..1 to 0..1 - - // Increase contrast: darker minimum, brighter maximum - // Old range: 0.4 to 1.0 (0.6 total range) - // New range: 0.2 to 1.2 (1.0 total range) - much more contrast - float contrastMultiplier = 0.2 + brightness * 1.0; - - // Create base terrain color modulated by geometry - vec3 baseColor = terrainColor; - vec3 finalColorRGB = baseColor * contrastMultiplier; - - finalColor = vec4(finalColorRGB, 1.0); -} - diff --git a/game/terrain.vs b/game/terrain.vs deleted file mode 100644 index 6764ee3..0000000 --- a/game/terrain.vs +++ /dev/null @@ -1,31 +0,0 @@ -#version 330 - -// Input vertex attributes -in vec3 vertexPosition; -in vec3 vertexNormal; -in vec2 vertexTexCoord; - -// Uniforms -uniform mat4 mvp; -uniform mat4 matModel; -uniform mat4 matNormal; - -// Output to fragment shader -out vec3 fragPosition; -out vec3 fragNormal; -out vec2 fragTexCoord; - -void main() -{ - // Transform vertex position to world space - fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); - - // Transform normal to world space - fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 0.0))); - - // Pass texture coordinates - fragTexCoord = vertexTexCoord; - - // Transform to clip space - gl_Position = mvp * vec4(vertexPosition, 1.0); -}