diff --git a/game/build_client.sh b/game/build_client.sh deleted file mode 100755 index 5b8fecc..0000000 --- a/game/build_client.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# Build the game client using the split modules - -set -e - -cd "$(dirname "$0")" - -# Get raylib compile and link flags -RAYLIB_CFLAGS=$(pkg-config --cflags raylib 2>/dev/null || echo "-I/usr/include") -RAYLIB_LIBS=$(pkg-config --libs raylib 2>/dev/null || echo "-lraylib -lm") - -echo "Compiling game modules..." -gcc -c suic_math.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_math.o -gcc -c suic_terrain.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_terrain.o -gcc -c suic_lighting.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_lighting.o -gcc -c suic_net.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_net.o -gcc -c suic_ui.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_ui.o -gcc -c suic_game_client.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_game_client.o - -echo "Compiling main entry point..." -gcc -c main.c -I../libsuicmez $RAYLIB_CFLAGS -o main.o - -echo "Linking..." -gcc main.o suic_math.o suic_terrain.o suic_lighting.o suic_net.o suic_ui.o suic_game_client.o \ - $RAYLIB_LIBS -lm -o game_client - -echo "Successfully built: game_client" -echo "Run with: ./game_client" diff --git a/game/client b/game/client new file mode 100755 index 0000000..989692a Binary files /dev/null and b/game/client differ diff --git a/game/client.c b/game/client.c index 65835e2..eb6df46 100644 --- a/game/client.c +++ b/game/client.c @@ -1,60 +1,1174 @@ -#include "libsuicmez/libsuicmez.h" +#include "raylib.h" +#include +#include #include -#include -#include #include -void* gc_alloc(const TypeInfo* type, size_t size); -void gc_init(void); -void gc_shutdown(void); +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#pragma comment(lib, "ws2_32.lib") +typedef int socklen_t; +#define CLOSESOCK closesocket +#else +#include +#include +#include +#include +#include +#define CLOSESOCK close +#endif -// Helper for allocating arrays -static void* suic_alloc_array(const TypeInfo* type, size_t elem_size, size_t len, void* init_data) { - void* ptr = gc_alloc(type, elem_size * len); - if (init_data) memcpy(ptr, init_data, elem_size * len); - return ptr; +#define PROTOCOL_VERSION 67 +#define SERVER_PORT 27015 +#define MAX_PLAYERS 16 +#define USERNAME_MAX 16 + +#define WEAPON_PISTOL 0 +#define WEAPON_RIFLE 1 + +#define ITEM_NONE 0 +#define ITEM_MEDKIT 1 +#define ITEM_AMMO_PISTOL 2 +#define ITEM_AMMO_RIFLE 3 + +#define MAX_ITEMS 64 + +#define BTN_RELOAD (1u << 0) +#define BTN_SWITCH_PISTOL (1u << 1) +#define BTN_SWITCH_RIFLE (1u << 2) +#define BTN_PICK (1u << 3) +#define BTN_USE_MEDKIT (1u << 4) +#define BTN_JUMP (1u << 5) + +typedef struct { + Vector3 direction; // Normalized light direction + Vector3 color; // RGB color (0-1 range) + float intensity; // Light intensity multiplier + float ambientIntensity; // Ambient light strength + float shadowBias; // Shadow bias to prevent z-fighting + 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; } -// Helper for allocating structs -static void* suic_alloc_struct(const TypeInfo* type, size_t size, void* init_data) { - void* ptr = gc_alloc(type, size); - if (init_data) memcpy(ptr, init_data, size); - return ptr; +static void unload_terrain_shader(TerrainShader *ts) { + if (ts && ts->shader.id != 0) { + UnloadShader(ts->shader); + ts->shader.id = 0; + } } +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, + 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, + 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, + 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, + 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, + 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, + 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, + 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, + 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, + 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, + 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, + 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, + 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, + 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, + 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, + 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, + 180, 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, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, + 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, + 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, + 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, + 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, + 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, + 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, + 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, + 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, + 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, + 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, + 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, + 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, + 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, + 156, 180}; -; -; +static float grad2(int hash, float x, float y) { + int h = hash & 7; + float u = h < 4 ? x : y; + float v = h < 4 ? y : x; + return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f * v : 2.0f * v); +} +static float simplex_noise_2d(float x, float y) { + const float F2 = 0.366025403f; + const float G2 = 0.211324865f; + float s = (x + y) * F2; + int i = (int)floorf(x + s); + int j = (int)floorf(y + s); + float t = (i + j) * G2; + float X0 = i - t, Y0 = j - t; + float x0 = x - X0, y0 = y - Y0; + int i1 = (x0 > y0) ? 1 : 0; + int j1 = (x0 > y0) ? 0 : 1; + float x1 = x0 - i1 + G2, y1 = y0 - j1 + G2; + float x2 = x0 - 1.0f + 2.0f * G2, y2 = y0 - 1.0f + 2.0f * G2; + int ii = i & 255, jj = j & 255; + float n0 = 0.0f, n1 = 0.0f, n2 = 0.0f; + float t0 = 0.5f - x0 * x0 - y0 * y0; + if (t0 >= 0.0f) { + t0 *= t0; + n0 = t0 * t0 * grad2(perm[ii + perm[jj]], x0, y0); + } + float t1 = 0.5f - x1 * x1 - y1 * y1; + if (t1 >= 0.0f) { + t1 *= t1; + n1 = t1 * t1 * grad2(perm[ii + i1 + perm[jj + j1]], x1, y1); + } + float t2 = 0.5f - x2 * x2 - y2 * y2; + if (t2 >= 0.0f) { + t2 *= t2; + n2 = t2 * t2 * grad2(perm[ii + 1 + perm[jj + 1]], x2, y2); + } + return 45.0f * (n0 + n1 + n2); +} +static float fbm_noise(float x, float y, int octaves) { + float value = 0.0f, amplitude = 1.0f, frequency = 1.0f, max_value = 0.0f; + for (int i = 0; i < octaves; i++) { + value += simplex_noise_2d(x * frequency, y * frequency) * amplitude; + max_value += amplitude; + amplitude *= 0.5f; + frequency *= 2.0f; + } + return value / max_value; +} +float get_terrain_height(float x, float z) { + float height = fbm_noise(x * 0.05f, z * 0.05f, 2); + height += fbm_noise(x * 0.01f, z * 0.01f, 2) * 1.5f; + return height * 4.0f + 2.0f; +} -int suic_main(void); +#define TERRAIN_SIZE 256 +#define TERRAIN_SCALE 1.0f +#define TERRAIN_MIN (-TERRAIN_SIZE * TERRAIN_SCALE / 2.0f) +#define TERRAIN_MAX (TERRAIN_SIZE * TERRAIN_SCALE / 2.0f) +static Mesh generate_terrain_mesh(void) { + int size = TERRAIN_SIZE; + Mesh mesh = {0}; -int suic_main(void) { - suic_init_window(1280, 720, suic_alloc_array(NULL, sizeof(char), 21, "Voxel Shooter Client")); - suic_set_target_fps(120); - suic_disable_cursor(); - while ((suic_window_should_close() == false)) { - float dt = suic_get_frame_time(); - suic_begin_drawing(); - suic_clear_background(135, 206, 235, 255); - DrawFPS(10, 10); - suic_end_drawing(); + int vertexCount = size * size; + int triangleCount = (size - 1) * (size - 1) * 2; + + mesh.vertexCount = vertexCount; + mesh.triangleCount = triangleCount; + + mesh.vertices = (float *)MemAlloc(vertexCount * 3 * sizeof(float)); + mesh.texcoords = (float *)MemAlloc(vertexCount * 2 * sizeof(float)); + mesh.normals = (float *)MemAlloc(vertexCount * 3 * sizeof(float)); + mesh.indices = + (unsigned short *)MemAlloc(triangleCount * 3 * sizeof(unsigned short)); + + // Generate vertices + 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; + float wy = get_terrain_height(wx, wz); + + mesh.vertices[idx * 3 + 0] = wx; + mesh.vertices[idx * 3 + 1] = wy; + mesh.vertices[idx * 3 + 2] = wz; + + mesh.texcoords[idx * 2 + 0] = (float)x / (float)(size - 1); + mesh.texcoords[idx * 2 + 1] = (float)z / (float)(size - 1); } - suic_close_window(); - return 0; + } + + // Generate indices + int triIdx = 0; + for (int z = 0; z < size - 1; z++) { + for (int x = 0; x < size - 1; x++) { + int i0 = z * size + x; + int i1 = z * size + (x + 1); + int i2 = (z + 1) * size + x; + int i3 = (z + 1) * size + (x + 1); + + mesh.indices[triIdx * 3 + 0] = i0; + mesh.indices[triIdx * 3 + 1] = i2; + mesh.indices[triIdx * 3 + 2] = i1; + triIdx++; + + mesh.indices[triIdx * 3 + 0] = i1; + mesh.indices[triIdx * 3 + 1] = i2; + mesh.indices[triIdx * 3 + 2] = i3; + triIdx++; + } + } + + // 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; + + // 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]; + + // 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); + + // Normal from height field: (-dh/dx, 1, -dh/dz) then normalized + float nx = -dh_dx; + float ny = 1.0f; + float nz = -dh_dz; + + 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; + } + } + } + + UploadMesh(&mesh, false); + return mesh; } -int main(int argc, char* argv[]) { - // Initialize GC - gc_init(); - // init globals - // init event loop - int result = suic_main(); - // Shutdown GC - gc_shutdown(); - return result; +// 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, + DirectionalLight light) { + // Normalize light direction (it's already normalized, but for safety) + Vector3 lightDir = light.direction; + + // Calculate diffuse component: dot product of negative light direction and + // surface normal We use negative because light travels opposite to its + // direction vector + float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y + + lightDir.z * normal.z)); + + // Combine diffuse with ambient light + // Ambient provides minimum brightness even in shadow + float brightness = light.ambientIntensity + + (diff * light.intensity * (1.0f - light.ambientIntensity)); + + // Apply brightness multiplier to base color + int r = (int)(baseColor.r * brightness); + int g = (int)(baseColor.g * brightness); + int b = (int)(baseColor.b * brightness); + + // Clamp RGB to valid range [0, 255] + if (r > 255) + r = 255; + if (g > 255) + g = 255; + if (b > 255) + b = 255; + + return (Color){r, g, b, baseColor.a}; } +// calculate_shadow_factor: Distance-based shadow softening +// Objects at higher elevations or farther from ground get softer, less +// pronounced shadows This simulates how shadows fade with distance and +// atmospheric scattering +static float calculate_shadow_factor(Vector3 worldPos, Vector3 lightDir, + float maxShadowDistance) { + // Calculate height above ground (approximate) + float distFromLight = fmaxf(0.0f, worldPos.y - 2.0f); + + // Fade out shadow strength with distance (max 15% darkening) + float shadowIntensity = + fmaxf(0.0f, 1.0f - (distFromLight / maxShadowDistance)); + return 1.0f - (shadowIntensity * 0.15f); +} + +// calculate_temporal_shadow: Time-based shadow variance for anti-aliasing +// Simulates subtle shadow movement to avoid banding artifacts +static float calculate_temporal_shadow(Vector3 worldPos, float timePhase) { + // Add subtle time-based variation to shadow boundaries + float noiseVal = + sinf(worldPos.x * 0.5f + timePhase) * cosf(worldPos.z * 0.5f + timePhase); + return 1.0f + (noiseVal * 0.02f); // Very subtle variation +} + +// apply_lighting_with_shadows: Full lighting calculation with shadows +// Combines directional light, ambient light, shadows, and temporal variation +static Color apply_lighting_with_shadows(Color baseColor, Vector3 normal, + Vector3 worldPos, + DirectionalLight light) { + // Calculate base shadow factor (distance-based) + float shadowFactor = + calculate_shadow_factor(worldPos, light.direction, 10.0f); + + // Apply shadow to intensity + float adjustedIntensity = light.intensity * shadowFactor; + + // Calculate diffuse component with adjusted intensity + Vector3 lightDir = light.direction; + float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y + + lightDir.z * normal.z)); + + // Combine with ambient using adjusted intensity + float brightness = light.ambientIntensity + (diff * adjustedIntensity * + (1.0f - light.ambientIntensity)); + + // Apply brightness to base color + int r = (int)(baseColor.r * brightness); + int g = (int)(baseColor.g * brightness); + int b = (int)(baseColor.b * brightness); + + // Clamp values 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}; +} + +static Vector3 v3_normalize(Vector3 v) { + float len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); + if (len < 0.0001f) + 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 Vector3 v3_mul(Vector3 a, float s) { + return v3(a.x * s, a.y * s, a.z * s); +} +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}; +} + +static void set_nonblocking(int sock) { +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(sock, FIONBIO, &mode); +#else + int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); +#endif +} + +#pragma pack(push, 1) +typedef enum MsgType : uint8_t { + MSG_HELLO = 1, + MSG_WELCOME = 2, + MSG_INPUT = 3, + MSG_SNAPSHOT = 4, + MSG_SHOOT = 5, + MSG_ITEMS = 6, + MSG_ROOM_STATE = 7 +} MsgType; + +typedef struct MsgHello { + uint8_t type; + uint32_t protocol; + char username[USERNAME_MAX]; +} MsgHello; + +typedef struct MsgWelcome { + uint8_t type; + uint8_t playerId; + uint32_t serverTick; +} MsgWelcome; + +typedef struct MsgInput { + uint8_t type; + uint8_t playerId; + uint32_t clientTick; + float moveX, moveZ, yaw, pitch; + uint8_t buttons; +} MsgInput; + +typedef struct MsgShoot { + uint8_t type; + uint8_t playerId; + uint32_t clientTick; +} MsgShoot; + +typedef struct PlayerStateNet { + uint8_t id, alive; + int16_t hp; + float x, y, z, yaw, pitch; + uint8_t weapon; + int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; + int16_t reloadTimeLeft; + char username[USERNAME_MAX]; +} PlayerStateNet; + +typedef struct MsgSnapshot { + uint8_t type; + uint32_t serverTick; + uint8_t count; + PlayerStateNet p[MAX_PLAYERS]; +} MsgSnapshot; + +typedef struct ItemNet { + uint16_t id; + uint8_t type; + int16_t qty; + float x, y, z; +} ItemNet; + +typedef struct MsgItems { + uint8_t type; + uint32_t serverTick; + 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 { + int present, alive, hp; + Vector3 pos; + Vector3 prevPos; + float yaw, pitch; + uint8_t weapon; + int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; + int16_t reloadTimeLeft; + char username[USERNAME_MAX]; +} RemotePlayer; + +typedef struct WorldItem { + int present; + uint16_t id; + uint8_t type; + int16_t qty; + Vector3 pos; +} WorldItem; + +static const char *ItemName(uint8_t t) { + switch (t) { + case ITEM_MEDKIT: + return "Medkit"; + case ITEM_AMMO_PISTOL: + return "Pistol ammo"; + case ITEM_AMMO_RIFLE: + return "Rifle ammo"; + default: + return "-"; + } +} + +static void ui_username_prompt(char outName[USERNAME_MAX]) { + memset(outName, 0, USERNAME_MAX); + while (!WindowShouldClose()) { + int ch = GetCharPressed(); + while (ch > 0) { + int len = (int)strlen(outName); + if (ch >= 32 && ch <= 126) { + if (len < USERNAME_MAX - 1) { + outName[len] = (char)ch; + outName[len + 1] = '\0'; + } + } + ch = GetCharPressed(); + } + if (IsKeyPressed(KEY_BACKSPACE)) { + int len = (int)strlen(outName); + if (len > 0) + outName[len - 1] = '\0'; + } + if (IsKeyPressed(KEY_ENTER) && strlen(outName) > 0) + return; + + BeginDrawing(); + ClearBackground((Color){20, 24, 32, 255}); + DrawText("Enter username (press ENTER):", 60, 80, 28, RAYWHITE); + DrawRectangle(60, 130, 420, 48, (Color){40, 48, 64, 255}); + DrawRectangleLines(60, 130, 420, 48, (Color){120, 140, 170, 255}); + DrawText(outName[0] ? outName : "_", 72, 142, 24, + (Color){230, 230, 240, 255}); + EndDrawing(); + } +} + +int main(void) { +#ifdef _WIN32 + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); +#endif + + const int sw = 1280, sh = 720; + InitWindow(sw, sh, "Voxel Shooter - Client (SMOOTH TERRAIN WITH SHADOWS)"); + SetTargetFPS(120); + + char myName[USERNAME_MAX]; + ui_username_prompt(myName); + + DisableCursor(); + + // Generate terrain mesh + Mesh terrainMesh = generate_terrain_mesh(); + Model terrainModel = LoadModelFromMesh(terrainMesh); + terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = + (Color){80, 140, 70, 255}; + + // Setup directional light + DirectionalLight dirLight = {0}; + dirLight.direction = + v3_normalize(v3(-0.8f, -1.0f, -0.6f)); // Coming from upper-left-back + dirLight.color = v3(1.0f, 1.0f, 1.0f); + dirLight.intensity = 1.2f; + dirLight.ambientIntensity = 0.3f; + 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"); + return 1; + } + set_nonblocking(sock); + + struct sockaddr_in srv = {0}; + srv.sin_family = AF_INET; + srv.sin_port = htons(SERVER_PORT); + inet_pton(AF_INET, "127.0.0.1", &srv.sin_addr); + + uint8_t myId = 255; + uint32_t clientTick = 0; + + 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; + + Vector3 prevBodyPos = v3(0, 5.0f, 6); + Vector3 currentTargetBody = v3(0, 5.0f, 6); + float interpTimer = 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; + + const float pistolFireRate = 4.0f, rifleFireRate = 12.0f; + const float recoilReturn = 18.0f, crossReturn = 14.0f; + const float pistolKickPitch = 0.010f, pistolKickYaw = 0.004f; + const float rifleKickPitch = 0.018f, rifleKickYaw = 0.010f; + const float pistolCrossKick = 2.0f, rifleCrossKick = 4.0f; + const float pistolSprayGrow = 0.4f, rifleSprayGrow = 1.2f; + const float burstResetTime = 0.18f; + + MsgHello hello = {0}; + hello.type = MSG_HELLO; + hello.protocol = PROTOCOL_VERSION; + strncpy(hello.username, myName, USERNAME_MAX - 1); + sendto(sock, (const char *)&hello, (int)sizeof(hello), 0, + (const struct sockaddr *)&srv, sizeof(srv)); + + while (!WindowShouldClose()) { + clientTick++; + float dt = GetFrameTime(); + + fireCooldown -= dt; + if (fireCooldown < 0.0f) + fireCooldown = 0.0f; + burstResetTimer -= dt; + if (burstResetTimer <= 0.0f) + shotsInBurst = 0; + + { + float k = 1.0f - expf(-recoilReturn * dt); + recoilYaw += (0.0f - recoilYaw) * k; + recoilPitch += (0.0f - recoilPitch) * k; + } + { + float k = 1.0f - expf(-crossReturn * dt); + crossSpread += (0.0f - crossSpread) * k; + if (crossSpread < 0.01f) + crossSpread = 0.0f; + } + + for (;;) { + uint8_t buf[1400]; + struct sockaddr_in from = {0}; + socklen_t fromLen = sizeof(from); + int n = (int)recvfrom(sock, (char *)buf, (int)sizeof(buf), 0, + (struct sockaddr *)&from, &fromLen); + if (n <= 0) { +#ifdef _WIN32 + if (WSAGetLastError() == WSAEWOULDBLOCK) + break; +#else + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; +#endif + break; + } + + uint8_t type = buf[0]; + if (type == MSG_WELCOME && n >= (int)sizeof(MsgWelcome)) { + MsgWelcome *w = (MsgWelcome *)buf; + myId = w->playerId; + } else if (type == MSG_SNAPSHOT && n >= (int)sizeof(MsgSnapshot)) { + MsgSnapshot *s = (MsgSnapshot *)buf; + for (int i = 0; i < MAX_PLAYERS; i++) + rp[i].present = 0; + + for (int i = 0; i < (int)s->count && i < MAX_PLAYERS; i++) { + PlayerStateNet *ps = &s->p[i]; + if (ps->id >= MAX_PLAYERS) + continue; + + RemotePlayer *p = &rp[ps->id]; + p->prevPos = p->pos; + p->present = 1; + p->alive = ps->alive; + p->hp = ps->hp; + p->pos = v3(ps->x, ps->y, ps->z); + p->yaw = ps->yaw; + p->pitch = ps->pitch; + p->weapon = ps->weapon; + p->pistolMag = ps->pistolMag; + p->rifleMag = ps->rifleMag; + 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); + } + + if (myId != 255 && rp[myId].present) { + prevBodyPos = currentTargetBody; + currentTargetBody = rp[myId].pos; + interpTimer = 0.0f; + } + } else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) { + MsgItems *m = (MsgItems *)buf; + for (int i = 0; i < MAX_ITEMS; i++) + wi[i].present = 0; + + for (int i = 0; i < (int)m->count && i < MAX_ITEMS; i++) { + wi[i].present = 1; + wi[i].id = m->items[i].id; + wi[i].type = m->items[i].type; + 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); + } + } + } + + // Interpolate camera position + if (myId != 255 && rp[myId].present) { + float interpFactor = interpTimer / (1.0f / 20.0f); + if (interpFactor > 1.0f) + interpFactor = 1.0f; + Vector3 interpBody = v3_add(v3_mul(prevBodyPos, 1.0f - interpFactor), + v3_mul(currentTargetBody, interpFactor)); + camPos.x = interpBody.x; + camPos.y = interpBody.y + 1.0f; + camPos.z = interpBody.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); + } + interpTimer += dt; + + Vector2 md = GetMouseDelta(); + const float sens = 0.0025f; + yaw -= md.x * sens; + pitch -= md.y * sens; + if (pitch < -1.5f) + pitch = -1.5f; + if (pitch > 1.5f) + pitch = 1.5f; + + float viewYaw = yaw + recoilYaw; + float viewPitch = pitch + recoilPitch; + if (viewPitch < -1.5f) + viewPitch = -1.5f; + if (viewPitch > 1.5f) + viewPitch = 1.5f; + + float moveX = 0.0f, moveZ = 0.0f; + if (IsKeyDown(KEY_A)) + moveX += 1.0f; + if (IsKeyDown(KEY_D)) + moveX -= 1.0f; + if (IsKeyDown(KEY_W)) + moveZ += 1.0f; + if (IsKeyDown(KEY_S)) + moveZ -= 1.0f; + + uint8_t buttons = 0; + if (IsKeyPressed(KEY_ONE)) + buttons |= BTN_SWITCH_PISTOL; + if (IsKeyPressed(KEY_TWO)) + buttons |= BTN_SWITCH_RIFLE; + if (IsKeyPressed(KEY_R)) + buttons |= BTN_RELOAD; + if (IsKeyPressed(KEY_F)) + buttons |= BTN_PICK; + if (IsKeyPressed(KEY_H)) + buttons |= BTN_USE_MEDKIT; + if (IsKeyPressed(KEY_SPACE)) + buttons |= BTN_JUMP; + if (IsKeyPressed(KEY_Z)) + scoped = !scoped; + + if (myId != 255) { + MsgInput in = {0}; + in.type = MSG_INPUT; + in.playerId = myId; + in.clientTick = clientTick; + in.moveX = moveX; + in.moveZ = moveZ; + in.yaw = viewYaw; + in.pitch = viewPitch; + in.buttons = buttons; + sendto(sock, (const char *)&in, (int)sizeof(in), 0, + (const struct sockaddr *)&srv, sizeof(srv)); + } + + if (myId != 255 && IsMouseButtonDown(MOUSE_BUTTON_LEFT) && + fireCooldown <= 0.0f && rp[myId].present) { + int wpn = rp[myId].weapon; + float rate = (wpn == WEAPON_PISTOL) ? pistolFireRate : rifleFireRate; + fireCooldown = 1.0f / rate; + + shotsInBurst++; + burstResetTimer = burstResetTime; + + if (wpn == WEAPON_PISTOL) { + recoilPitch += pistolKickPitch; + recoilYaw += + (((float)GetRandomValue(-1000, 1000)) / 1000.0f) * pistolKickYaw; + crossSpread += pistolCrossKick + shotsInBurst * pistolSprayGrow; + } else { + recoilPitch += rifleKickPitch; + recoilYaw += + (((float)GetRandomValue(-1000, 1000)) / 1000.0f) * rifleKickYaw; + crossSpread += rifleCrossKick + shotsInBurst * rifleSprayGrow; + } + + MsgShoot shmsg = {0}; + shmsg.type = MSG_SHOOT; + shmsg.playerId = myId; + shmsg.clientTick = clientTick; + sendto(sock, (const char *)&shmsg, (int)sizeof(shmsg), 0, + (const struct sockaddr *)&srv, sizeof(srv)); + } + + Vector3 forward = v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch), + cosf(viewYaw) * cosf(viewPitch)); + + // Offset camera position: 0.3m in front, 0.2m below + camPos = v3_add(camPos, v3_mul(forward, 0.3f)); + camPos.y -= 0.2f; + + // Ensure camera doesn't clip into terrain after offset + float terrain_h = get_terrain_height(camPos.x, camPos.z); + camPos.y = fmaxf(camPos.y, terrain_h + 1.5f); + + Camera3D cam = {0}; + cam.position = camPos; + cam.target = v3_add(camPos, forward); + cam.up = v3(0, 1, 0); + cam.fovy = scoped ? 30.0f : 75.0f; + cam.projection = CAMERA_PERSPECTIVE; + + BeginDrawing(); + ClearBackground((Color){135, 206, 235, 255}); + + 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}; + + // Debug mode: 0=normal lighting, 1=show normals as colors, 2=show AO only + + 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}; + DrawModel(terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE); + + // Draw border + float borderSize = TERRAIN_MAX - TERRAIN_MIN; + DrawCube((Vector3){0, 0, 0}, borderSize, 1000, borderSize, (Color){255, 0, 0, 100}); + + // Items with basic lighting (no shader for now to keep it simple) + for (int i = 0; i < MAX_ITEMS; i++) { + if (!wi[i].present) + continue; + Color ic = (Color){220, 220, 220, 255}; + if (wi[i].type == ITEM_MEDKIT) + ic = (Color){120, 255, 120, 255}; + if (wi[i].type == ITEM_AMMO_PISTOL) + ic = (Color){255, 220, 120, 255}; + if (wi[i].type == ITEM_AMMO_RIFLE) + ic = (Color){255, 180, 120, 255}; + + // Apply basic lighting to items + Vector3 itemNormal = v3(0, 1, 0); + Color litColor = + apply_lighting_with_shadows(ic, itemNormal, wi[i].pos, dirLight); + DrawSphere(wi[i].pos, 0.3f, litColor); + } + + // Players with basic lighting + for (int i = 0; i < MAX_PLAYERS; i++) { + if (!rp[i].present) + continue; + float interpFactor = interpTimer / (1.0f / 20.0f); + if (interpFactor > 1.0f) + interpFactor = 1.0f; + Vector3 p = v3_add(v3_mul(rp[i].prevPos, 1.0f - interpFactor), + v3_mul(rp[i].pos, interpFactor)); + Color c = + (i == myId) ? (Color){80, 180, 255, 255} : (Color){255, 80, 80, 255}; + if (!rp[i].alive) + c = (Color){120, 120, 120, 255}; + + // Apply basic lighting to players + Vector3 playerNormal = v3(0, 1, 0); + Color litPlayerColor = + apply_lighting_with_shadows(c, playerNormal, 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); + } + + EndMode3D(); + + // Nameplates + for (int i = 0; i < MAX_PLAYERS; i++) { + if (!rp[i].present || !rp[i].username[0]) + continue; + float interpFactor = interpTimer / (1.0f / 20.0f); + if (interpFactor > 1.0f) + interpFactor = 1.0f; + Vector3 p = v3_add(v3_mul(rp[i].prevPos, 1.0f - interpFactor), + v3_mul(rp[i].pos, interpFactor)); + Vector3 head = v3(p.x, p.y + 1.2f, p.z); + Vector3 camForward = v3_norm(v3_sub(cam.target, cam.position)); + Vector3 toHead = v3_sub(head, cam.position); + if (v3_dot(camForward, toHead) <= 0.0f) + continue; + Vector2 s = GetWorldToScreen(head, cam); + if (s.x < -200 || s.x > sw + 200 || s.y < -200 || s.y > sh + 200) + continue; + int fontSize = 18; + int w = MeasureText(rp[i].username, fontSize); + Color tc = (i == myId) ? (Color){180, 230, 255, 255} : RAYWHITE; + DrawText(rp[i].username, (int)(s.x - w / 2), (int)(s.y - fontSize), + fontSize, tc); + } + + // HUD + if (myId == 255 || !rp[myId].present) { + DrawText("Connecting...", 10, 10, 20, RAYWHITE); + } else { + DrawRectangle(10, 10, 280, 110, (Color){0, 0, 0, 120}); + DrawText("HP", 20, 20, 20, RAYWHITE); + int healthBarWidth = (rp[myId].hp * 220) / 100; + Color healthColor = (rp[myId].hp > 60) ? (Color){80, 255, 80, 120} + : (rp[myId].hp > 30) ? (Color){255, 200, 80, 120} + : (Color){255, 80, 80, 120}; + DrawRectangle(20, 45, 220, 20, (Color){40, 40, 40, 255}); + DrawRectangle(20, 45, healthBarWidth, 20, healthColor); + DrawText(TextFormat("%d", rp[myId].hp), 250, 47, 18, RAYWHITE); + DrawText(TextFormat("Medkits: %d (H to use)", rp[myId].medkits), 20, 75, + 18, + (rp[myId].medkits > 0) ? (Color){120, 255, 120, 120} + : (Color){120, 120, 120, 120}); + DrawText("1=Pistol 2=Rifle R=Reload F=Pick SPACE=Jump", 20, 95, 12, + (Color){150, 150, 150, 150}); + + const char *weaponName = + (rp[myId].weapon == WEAPON_PISTOL) ? "PISTOL" : "RIFLE"; + Color weaponColor = (rp[myId].weapon == WEAPON_PISTOL) + ? (Color){100, 200, 255, 255} + : (Color){255, 150, 100, 255}; + int currentMag = (rp[myId].weapon == WEAPON_PISTOL) ? rp[myId].pistolMag + : rp[myId].rifleMag; + int reserveAmmo = (rp[myId].weapon == WEAPON_PISTOL) ? rp[myId].pistolAmmo + : rp[myId].rifleAmmo; + + DrawRectangle(sw - 260, sh - 120, 250, 110, (Color){0, 0, 0, 180}); + DrawText(weaponName, sw - 250, sh - 110, 28, weaponColor); + 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) + 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, 350, 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, 350, 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, 450, 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; + 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}); + } + + EndDrawing(); + } + + UnloadModel(terrainModel); + unload_terrain_shader(&terrainShader); + CloseWindow(); + CLOSESOCK(sock); +#ifdef _WIN32 + WSACleanup(); +#endif + return 0; +} diff --git a/game/client.o b/game/client.o new file mode 100644 index 0000000..c6e1085 Binary files /dev/null and b/game/client.o differ diff --git a/game/client.sui b/game/client.sui index 7f90f69..e69de29 100644 --- a/game/client.sui +++ b/game/client.sui @@ -1,22 +0,0 @@ -fn main() -> int do - init_window(1280, 720, "Voxel Shooter Client") - defer close_window() - set_target_fps(120) - disable_cursor() - - # Main loop - while window_should_close() == false do - let dt = get_frame_time() - - # Rendering - begin_drawing() - clear_background(135, 206, 235, 255) - - # Draw FPS - draw_fps(10, 10) - - end_drawing() - end - - 0 -end \ No newline at end of file diff --git a/game/game_client b/game/game_client deleted file mode 100755 index 58f9058..0000000 Binary files a/game/game_client and /dev/null differ diff --git a/game/main.c b/game/main.c deleted file mode 100644 index 4eb2849..0000000 --- a/game/main.c +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Game Client Main Entry Point - * Uses the split modules: suic_game_client, suic_terrain, suic_lighting, suic_net, suic_ui - */ - -#include "suic_game_client.h" -#include "suic_ui.h" -#include "raylib.h" - -int main(void) { - const int screenW = 1280, screenH = 720; - InitWindow(screenW, screenH, "Voxel Shooter - Client"); - SetTargetFPS(120); - - /* Get username */ - char username[SUIC_NET_USERNAME_MAX] = {0}; - suic_ui_username_prompt(username, SUIC_NET_USERNAME_MAX); - - /* Initialize game subsystems */ - suic_game_init(username, "127.0.0.1", 27015); - - /* Main loop */ - while (!WindowShouldClose()) { - float dt = GetFrameTime(); - - suic_game_handle_input(dt); - suic_game_update(dt); - - BeginDrawing(); - ClearBackground((Color){135, 206, 235, 255}); - - suic_game_render(); - - EndDrawing(); - } - - suic_game_shutdown(); - CloseWindow(); - - return 0; -} diff --git a/game/main.o b/game/main.o deleted file mode 100644 index e3057a3..0000000 Binary files a/game/main.o and /dev/null differ diff --git a/game/runtime.h b/game/runtime.h deleted file mode 100644 index 8e97b2f..0000000 --- a/game/runtime.h +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include -#include "../libsuicmez/suicmez_gc.h" - -void* gc_alloc(const TypeInfo* type, size_t size); -void gc_init(void); -void gc_shutdown(void); - -// Helper for allocating arrays -void* suic_alloc_array(const TypeInfo* type, size_t elem_size, size_t len, void* init_data) { - void* ptr = gc_alloc(type, elem_size * len); - if (init_data) memcpy(ptr, init_data, elem_size * len); - return ptr; -} - -// Helper for allocating structs -void* suic_alloc_struct(const TypeInfo* type, size_t size, void* init_data) { - void* ptr = gc_alloc(type, size); - if (init_data) memcpy(ptr, init_data, size); - return ptr; -} \ No newline at end of file diff --git a/game/runtime.h.gch b/game/runtime.h.gch deleted file mode 100644 index 6bdc97a..0000000 Binary files a/game/runtime.h.gch and /dev/null differ diff --git a/game/suic_app.c b/game/suic_app.c deleted file mode 100644 index c024e0c..0000000 --- a/game/suic_app.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "suic_app.h" -#include "../libsuicmez/libsuicmez.h" -#include "runtime.h" - -void suic_app_init(int width, int height, const char* title) { - suic_init_window(width, height, (char*)title); - suic_set_target_fps(120); - suic_disable_cursor(); -} - -bool suic_app_should_close(void) { - return suic_window_should_close(); -} - -void suic_app_frame_begin(void) { - suic_begin_drawing(); - suic_clear_background(135, 206, 235, 255); -} - -void suic_app_frame_end(void) { - draw_fps(10, 40); - suic_end_drawing(); -} - -void suic_app_shutdown(void) { - suic_close_window(); -} - diff --git a/game/suic_app.h b/game/suic_app.h deleted file mode 100644 index f7a5cb6..0000000 --- a/game/suic_app.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include -#include - -/* ---- Lifecycle ---- */ -void suic_runtime_init(void); -void suic_runtime_shutdown(void); - -/* ---- Window / Loop ---- */ -void suic_app_init(int width, int height, const char* title); -bool suic_app_should_close(void); -void suic_app_frame_begin(void); -void suic_app_frame_end(void); -void suic_app_shutdown(void); - -/* ---- Drawing ---- */ -void suic_draw_hello(void); - -/* ---- Gameplay helpers ---- */ -const char* suic_item_name(uint8_t t); -struct Color* suic_apply_directional_lighting( - struct Color* baseColor, - struct Vector3* normal, - struct DirectionalLight* light -); diff --git a/game/suic_game.sui b/game/suic_game.sui deleted file mode 100644 index 114b231..0000000 --- a/game/suic_game.sui +++ /dev/null @@ -1,19 +0,0 @@ -fn main() -> int do - suic_runtime_init() - defer suic_runtime_shutdown() - - # Initialize application / window - suic_app_init(1280, 720, "Voxel Shooter - Client") - defer suic_app_shutdown() - - # Main loop - while not suic_app_should_close() do - suic_app_frame_begin() - - suic_draw_hello() - - suic_app_frame_end() - end - - 0 -end diff --git a/game/suic_game_client.c b/game/suic_game_client.c deleted file mode 100644 index f6d6cdc..0000000 --- a/game/suic_game_client.c +++ /dev/null @@ -1,280 +0,0 @@ -#include "suic_game_client.h" -#include "raylib.h" -#include -#include - -/* ===== GAME STATE ===== */ - -static SuicVec3 g_camPos = {0, 5.0f, 6}; -static float g_yaw = 0.0f, g_pitch = 0.0f; -static SuicVec3 g_prevBodyPos = {0, 5.0f, 6}; -static SuicVec3 g_currentTargetBody = {0, 5.0f, 6}; -static float g_interpTimer = 0.0f; - -static float g_recoilYaw = 0.0f, g_recoilPitch = 0.0f, g_crossSpread = 0.0f; -static bool g_scoped = false; -static float g_fireCooldown = 0.0f; -static int g_shotsInBurst = 0; -static float g_burstResetTimer = 0.0f; -static uint32_t g_clientTick = 0; - -static SuicDirectionalLight g_light; - -/* Weapon constants */ -static const float PISTOL_FIRE_RATE = 4.0f, RIFLE_FIRE_RATE = 12.0f; -static const float RECOIL_RETURN = 18.0f, CROSS_RETURN = 14.0f; -static const float PISTOL_KICK_PITCH = 0.010f, PISTOL_KICK_YAW = 0.004f; -static const float RIFLE_KICK_PITCH = 0.018f, RIFLE_KICK_YAW = 0.010f; -static const float PISTOL_CROSS_KICK = 2.0f, RIFLE_CROSS_KICK = 4.0f; -static const float PISTOL_SPRAY_GROW = 0.4f, RIFLE_SPRAY_GROW = 1.2f; -static const float BURST_RESET_TIME = 0.18f; - -/* ===== LIFECYCLE ===== */ - -void suic_game_init(const char* username, const char* serverIp, int port) { - suic_terrain_init(); - suic_net_init(serverIp, port); - suic_net_send_hello(username); - g_light = suic_light_create_default(); - g_camPos = suic_v3(0, 5.0f, 6); - g_yaw = 0.0f; - g_pitch = 0.0f; - DisableCursor(); -} - -void suic_game_shutdown(void) { - suic_terrain_cleanup(); - suic_net_shutdown(); -} - -/* ===== INPUT HANDLING ===== */ - -void suic_game_handle_input(float dt) { - g_clientTick++; - - /* Cooldowns */ - g_fireCooldown -= dt; - if (g_fireCooldown < 0.0f) g_fireCooldown = 0.0f; - g_burstResetTimer -= dt; - if (g_burstResetTimer <= 0.0f) g_shotsInBurst = 0; - - /* Recoil recovery */ - float k = 1.0f - expf(-RECOIL_RETURN * dt); - g_recoilYaw += (0.0f - g_recoilYaw) * k; - g_recoilPitch += (0.0f - g_recoilPitch) * k; - - float k2 = 1.0f - expf(-CROSS_RETURN * dt); - g_crossSpread += (0.0f - g_crossSpread) * k2; - if (g_crossSpread < 0.01f) g_crossSpread = 0.0f; - - /* Mouse look */ - Vector2 md = GetMouseDelta(); - const float sens = 0.0025f; - g_yaw -= md.x * sens; - g_pitch -= md.y * sens; - if (g_pitch < -1.5f) g_pitch = -1.5f; - if (g_pitch > 1.5f) g_pitch = 1.5f; - - /* Movement input */ - float moveX = 0.0f, moveZ = 0.0f; - if (IsKeyDown(KEY_A)) moveX += 1.0f; - if (IsKeyDown(KEY_D)) moveX -= 1.0f; - if (IsKeyDown(KEY_W)) moveZ += 1.0f; - if (IsKeyDown(KEY_S)) moveZ -= 1.0f; - - /* Buttons */ - uint8_t buttons = 0; - if (IsKeyPressed(KEY_ONE)) buttons |= SUIC_BTN_SWITCH_PISTOL; - if (IsKeyPressed(KEY_TWO)) buttons |= SUIC_BTN_SWITCH_RIFLE; - if (IsKeyPressed(KEY_R)) buttons |= SUIC_BTN_RELOAD; - if (IsKeyPressed(KEY_F)) buttons |= SUIC_BTN_PICK; - if (IsKeyPressed(KEY_H)) buttons |= SUIC_BTN_USE_MEDKIT; - if (IsKeyPressed(KEY_SPACE)) buttons |= SUIC_BTN_JUMP; - if (IsKeyPressed(KEY_Z)) g_scoped = !g_scoped; - - uint8_t myId = suic_net_get_my_id(); - float viewYaw = g_yaw + g_recoilYaw; - float viewPitch = g_pitch + g_recoilPitch; - if (viewPitch < -1.5f) viewPitch = -1.5f; - if (viewPitch > 1.5f) viewPitch = 1.5f; - - if (myId != 255) { - suic_net_send_input(myId, g_clientTick, moveX, moveZ, viewYaw, viewPitch, buttons); - } - - /* Shooting */ - if (myId != 255 && IsMouseButtonDown(MOUSE_BUTTON_LEFT) && g_fireCooldown <= 0.0f && - suic_net_player_present(myId)) { - int wpn = suic_net_player_weapon(myId); - float rate = (wpn == SUIC_WEAPON_PISTOL) ? PISTOL_FIRE_RATE : RIFLE_FIRE_RATE; - g_fireCooldown = 1.0f / rate; - - g_shotsInBurst++; - g_burstResetTimer = BURST_RESET_TIME; - - if (wpn == SUIC_WEAPON_PISTOL) { - g_recoilPitch += PISTOL_KICK_PITCH; - g_recoilYaw += (((float)GetRandomValue(-1000, 1000)) / 1000.0f) * PISTOL_KICK_YAW; - g_crossSpread += PISTOL_CROSS_KICK + g_shotsInBurst * PISTOL_SPRAY_GROW; - } else { - g_recoilPitch += RIFLE_KICK_PITCH; - g_recoilYaw += (((float)GetRandomValue(-1000, 1000)) / 1000.0f) * RIFLE_KICK_YAW; - g_crossSpread += RIFLE_CROSS_KICK + g_shotsInBurst * RIFLE_SPRAY_GROW; - } - - suic_net_send_shoot(myId, g_clientTick); - } -} - -/* ===== UPDATE ===== */ - -void suic_game_update(float dt) { - suic_net_poll(); - - uint8_t myId = suic_net_get_my_id(); - if (myId != 255 && suic_net_player_present(myId)) { - g_prevBodyPos = g_currentTargetBody; - g_currentTargetBody = suic_v3(suic_net_player_x(myId), - suic_net_player_y(myId), - suic_net_player_z(myId)); - g_interpTimer = 0.0f; - } - - /* Interpolate camera */ - if (myId != 255 && suic_net_player_present(myId)) { - float interpFactor = g_interpTimer / (1.0f / 20.0f); - if (interpFactor > 1.0f) interpFactor = 1.0f; - SuicVec3 interpBody = suic_v3_add(suic_v3_mul(g_prevBodyPos, 1.0f - interpFactor), - suic_v3_mul(g_currentTargetBody, interpFactor)); - g_camPos.x = interpBody.x; - g_camPos.y = interpBody.y + 1.0f; - g_camPos.z = interpBody.z + 0.0001f; - - float terrain_h = suic_terrain_height(g_camPos.x, g_camPos.z); - g_camPos.y = fmaxf(g_camPos.y, terrain_h + 1.5f); - } - g_interpTimer += dt; - - /* Forward offset */ - float viewYaw = g_yaw + g_recoilYaw; - float viewPitch = g_pitch + g_recoilPitch; - SuicVec3 forward = suic_v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch), - cosf(viewYaw) * cosf(viewPitch)); - g_camPos = suic_v3_add(g_camPos, suic_v3_mul(forward, 0.3f)); - g_camPos.y -= 0.2f; - - float terrain_h = suic_terrain_height(g_camPos.x, g_camPos.z); - g_camPos.y = fmaxf(g_camPos.y, terrain_h + 1.5f); -} - -/* ===== RENDERING ===== */ - -void suic_game_render(void) { - int sw = GetScreenWidth(); - int sh = GetScreenHeight(); - uint8_t myId = suic_net_get_my_id(); - - float viewYaw = g_yaw + g_recoilYaw; - float viewPitch = g_pitch + g_recoilPitch; - if (viewPitch < -1.5f) viewPitch = -1.5f; - if (viewPitch > 1.5f) viewPitch = 1.5f; - - SuicVec3 forward = suic_v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch), - cosf(viewYaw) * cosf(viewPitch)); - - Camera3D cam = {0}; - cam.position = (Vector3){g_camPos.x, g_camPos.y, g_camPos.z}; - cam.target = (Vector3){g_camPos.x + forward.x, g_camPos.y + forward.y, g_camPos.z + forward.z}; - cam.up = (Vector3){0, 1, 0}; - cam.fovy = g_scoped ? 30.0f : 75.0f; - cam.projection = CAMERA_PERSPECTIVE; - - BeginMode3D(cam); - - /* Terrain */ - suic_terrain_draw(g_camPos); - - /* Border */ - float borderSize = SUIC_TERRAIN_MAX - SUIC_TERRAIN_MIN; - DrawCube((Vector3){0, 0, 0}, borderSize, 1000, borderSize, (Color){255, 0, 0, 100}); - - /* Items */ - for (int i = 0; i < SUIC_NET_MAX_ITEMS; i++) { - if (!suic_net_item_present(i)) continue; - Color ic = (Color){220, 220, 220, 255}; - uint8_t itemType = suic_net_item_type(i); - if (itemType == SUIC_ITEM_MEDKIT) ic = (Color){120, 255, 120, 255}; - if (itemType == SUIC_ITEM_AMMO_PISTOL) ic = (Color){255, 220, 120, 255}; - if (itemType == SUIC_ITEM_AMMO_RIFLE) ic = (Color){255, 180, 120, 255}; - - SuicVec3 itemPos = suic_v3(suic_net_item_x(i), suic_net_item_y(i), suic_net_item_z(i)); - SuicVec3 itemNormal = suic_v3(0, 1, 0); - SuicColor litColor = suic_light_apply_shadows((SuicColor){ic.r, ic.g, ic.b, ic.a}, - itemNormal, itemPos, &g_light); - DrawSphere((Vector3){itemPos.x, itemPos.y, itemPos.z}, 0.3f, - (Color){litColor.r, litColor.g, litColor.b, litColor.a}); - } - - /* Players */ - for (int i = 0; i < SUIC_NET_MAX_PLAYERS; i++) { - if (!suic_net_player_present(i)) continue; - float interpFactor = g_interpTimer / (1.0f / 20.0f); - if (interpFactor > 1.0f) interpFactor = 1.0f; - SuicVec3 prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i)); - SuicVec3 curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i)); - SuicVec3 p = suic_v3_add(suic_v3_mul(prev, 1.0f - interpFactor), suic_v3_mul(curr, interpFactor)); - - Color c = (i == myId) ? (Color){80, 180, 255, 255} : (Color){255, 80, 80, 255}; - if (!suic_net_player_alive(i)) c = (Color){120, 120, 120, 255}; - - SuicVec3 playerNormal = suic_v3(0, 1, 0); - SuicColor litColor = suic_light_apply_shadows((SuicColor){c.r, c.g, c.b, c.a}, - playerNormal, p, &g_light); - DrawCapsule((Vector3){p.x, p.y - 0.5f, p.z}, (Vector3){p.x, p.y + 0.5f, p.z}, 0.35f, 8, 8, - (Color){litColor.r, litColor.g, litColor.b, litColor.a}); - } - - EndMode3D(); - - /* Nameplates */ - for (int i = 0; i < SUIC_NET_MAX_PLAYERS; i++) { - if (!suic_net_player_present(i)) continue; - float interpFactor = g_interpTimer / (1.0f / 20.0f); - if (interpFactor > 1.0f) interpFactor = 1.0f; - SuicVec3 prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i)); - SuicVec3 curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i)); - SuicVec3 p = suic_v3_add(suic_v3_mul(prev, 1.0f - interpFactor), suic_v3_mul(curr, interpFactor)); - - suic_ui_draw_nameplate(sw, sh, p.x, p.y, p.z, suic_net_player_username(i), i == myId, - g_camPos.x, g_camPos.y, g_camPos.z, - g_camPos.x + forward.x, g_camPos.y + forward.y, g_camPos.z + forward.z); - } - - /* HUD */ - if (myId == 255 || !suic_net_player_present(myId)) { - DrawText("Connecting...", 10, 10, 20, RAYWHITE); - } else { - int weapon = suic_net_player_weapon(myId); - int mag = (weapon == SUIC_WEAPON_PISTOL) ? suic_net_player_pistol_mag(myId) - : suic_net_player_rifle_mag(myId); - int reserve = (weapon == SUIC_WEAPON_PISTOL) ? suic_net_player_pistol_ammo(myId) - : suic_net_player_rifle_ammo(myId); - suic_ui_draw_hud(suic_net_player_hp(myId), weapon, mag, reserve, - suic_net_player_medkits(myId), suic_net_player_reload_time(myId)); - } - - DrawFPS(sw - 90, 10); - - /* Room state */ - suic_ui_draw_room_state(sw, sh, suic_net_room_state(), suic_net_countdown(), suic_net_winner_name()); - - /* Crosshair */ - suic_ui_draw_crosshair(sw, sh, g_crossSpread, g_scoped); -} - -/* ===== ACCESSORS ===== */ - -SuicVec3 suic_game_get_camera_pos(void) { return g_camPos; } -float suic_game_get_yaw(void) { return g_yaw; } -float suic_game_get_pitch(void) { return g_pitch; } -bool suic_game_is_scoped(void) { return g_scoped; } diff --git a/game/suic_game_client.h b/game/suic_game_client.h deleted file mode 100644 index 20880bb..0000000 --- a/game/suic_game_client.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include "suic_math.h" -#include "suic_terrain.h" -#include "suic_lighting.h" -#include "suic_net.h" -#include "suic_ui.h" -#include - -/** - * High-level game client API. - * Ties together terrain, networking, lighting, and rendering. - */ - -/* Game lifecycle */ -void suic_game_init(const char* username, const char* serverIp, int port); -void suic_game_update(float dt); -void suic_game_render(void); -void suic_game_shutdown(void); - -/* Camera state */ -SuicVec3 suic_game_get_camera_pos(void); -float suic_game_get_yaw(void); -float suic_game_get_pitch(void); -bool suic_game_is_scoped(void); - -/* Input state */ -void suic_game_handle_input(float dt); diff --git a/game/suic_game_client.o b/game/suic_game_client.o deleted file mode 100644 index 53111cf..0000000 Binary files a/game/suic_game_client.o and /dev/null differ diff --git a/game/suic_gameplay.c b/game/suic_gameplay.c deleted file mode 100644 index 9ec4781..0000000 --- a/game/suic_gameplay.c +++ /dev/null @@ -1,39 +0,0 @@ -#include "suic_app.h" -#include "runtime.h" -#include - -const char* suic_item_name(uint8_t t) { - if (t == 1) return "Medkit"; - if (t == 2) return "Pistol ammo"; - if (t == 3) return "Rifle ammo"; - return "-"; -} - -struct Color* suic_apply_directional_lighting( - struct Color* baseColor, - struct Vector3* normal, - struct DirectionalLight* light -) { - struct Vector3* lightDir = light->direction; - - float diff = fmaxf( - 0.0f, - -(lightDir->x * normal->x + - lightDir->y * normal->y + - lightDir->z * normal->z) - ); - - float brightness = - light->ambientIntensity + - diff * light->intensity * (1.0f - light->ambientIntensity); - - uint8_t r = (uint8_t)(baseColor->r * brightness); - uint8_t g = (uint8_t)(baseColor->g * brightness); - uint8_t b = (uint8_t)(baseColor->b * brightness); - - return suic_alloc_struct( - &sui_typeinfo_Color, - sizeof(struct Color), - &(struct Color){ r, g, b, baseColor->a } - ); -} \ No newline at end of file diff --git a/game/suic_lighting.c b/game/suic_lighting.c deleted file mode 100644 index 94a3239..0000000 --- a/game/suic_lighting.c +++ /dev/null @@ -1,150 +0,0 @@ -#include "suic_lighting.h" -#include "suic_terrain.h" -#include - -/* ===== LIGHT CREATION ===== */ - -SuicDirectionalLight suic_light_create(float dir_x, float dir_y, float dir_z, - float intensity, float ambient) { - SuicDirectionalLight light = {0}; - light.direction = suic_v3_norm(suic_v3(dir_x, dir_y, dir_z)); - light.color = suic_v3(1.0f, 1.0f, 1.0f); - light.intensity = intensity; - light.ambientIntensity = ambient; - light.shadowBias = 0.005f; - light.shadowIntensity = 0.4f; - return light; -} - -SuicDirectionalLight suic_light_create_default(void) { - return suic_light_create(-0.8f, -1.0f, -0.6f, 1.2f, 0.3f); -} - -/* ===== BASIC LIGHTING ===== */ - -SuicColor suic_light_apply(SuicColor base, SuicVec3 normal, SuicDirectionalLight* light) { - SuicVec3 lightDir = light->direction; - - /* Diffuse: Lambertian */ - float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y + - lightDir.z * normal.z)); - - float brightness = light->ambientIntensity + - (diff * light->intensity * (1.0f - light->ambientIntensity)); - - int r = (int)(base.r * brightness); - int g = (int)(base.g * brightness); - int b = (int)(base.b * brightness); - - if (r > 255) r = 255; - if (g > 255) g = 255; - if (b > 255) b = 255; - - return (SuicColor){r, g, b, base.a}; -} - -/* ===== SHADOW CALCULATIONS ===== */ - -float suic_shadow_factor(SuicVec3 worldPos, SuicVec3 lightDir, float maxDist) { - float distFromLight = fmaxf(0.0f, worldPos.y - 2.0f); - float shadowIntensity = fmaxf(0.0f, 1.0f - (distFromLight / maxDist)); - return 1.0f - (shadowIntensity * 0.15f); -} - -float suic_shadow_temporal(SuicVec3 worldPos, float timePhase) { - float noiseVal = sinf(worldPos.x * 0.5f + timePhase) * - cosf(worldPos.z * 0.5f + timePhase); - return 1.0f + (noiseVal * 0.02f); -} - -/* ===== LIGHTING WITH SHADOWS ===== */ - -SuicColor suic_light_apply_shadows(SuicColor base, SuicVec3 normal, SuicVec3 worldPos, - SuicDirectionalLight* light) { - float shadowFactor = suic_shadow_factor(worldPos, light->direction, 10.0f); - float adjustedIntensity = light->intensity * shadowFactor; - - SuicVec3 lightDir = light->direction; - float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y + - lightDir.z * normal.z)); - - float brightness = light->ambientIntensity + - (diff * adjustedIntensity * (1.0f - light->ambientIntensity)); - - int r = (int)(base.r * brightness); - int g = (int)(base.g * brightness); - int b = (int)(base.b * brightness); - - if (r > 255) r = 255; - if (g > 255) g = 255; - if (b > 255) b = 255; - - return (SuicColor){r, g, b, base.a}; -} - -/* ===== AMBIENT OCCLUSION ===== */ - -float suic_ao_calculate(SuicVec3 worldPos, SuicVec3 normal) { - float sampleRadius = 3.0f; - float aoAccum = 0.0f; - int numSamples = 8; - float centerHeight = worldPos.y; - float pi = 3.14159265f; - - for (int i = 0; i < numSamples; i++) { - float angle = (2.0f * pi * (float)i) / (float)numSamples; - float sx = worldPos.x + cosf(angle) * sampleRadius; - float sz = worldPos.z + sinf(angle) * sampleRadius; - float sh = suic_terrain_height(sx, sz); - - float heightDiff = sh - centerHeight; - if (heightDiff > 0.1f) { - float aoAmount = fminf(1.0f, heightDiff / 2.0f); - aoAccum += aoAmount; - } - } - - float aoFactor = 1.0f - (aoAccum / (float)numSamples) * 0.6f; - return fmaxf(0.2f, aoFactor); -} - -/* ===== PHONG LIGHTING ===== */ - -SuicColor suic_light_apply_phong(SuicColor base, SuicVec3 normal, SuicVec3 worldPos, - SuicVec3 camPos, SuicDirectionalLight* light) { - SuicVec3 lightDir = suic_v3_norm(light->direction); - SuicVec3 normal_n = suic_v3_norm(normal); - SuicVec3 viewDir = suic_v3_norm(suic_v3_sub(camPos, worldPos)); - - /* Diffuse */ - float diffuse = fmaxf(0.0f, -suic_v3_dot(lightDir, normal_n)); - - /* Specular: Blinn-Phong */ - SuicVec3 negLight = suic_v3(-lightDir.x, -lightDir.y, -lightDir.z); - SuicVec3 halfVec = suic_v3_norm(suic_v3_add(suic_v3_norm(negLight), viewDir)); - float specular = powf(fmaxf(0.0f, suic_v3_dot(halfVec, normal_n)), 32.0f) * 0.5f; - - /* Ambient occlusion */ - float ao = suic_ao_calculate(worldPos, normal_n); - - /* Shadow */ - float shadowFactor = suic_shadow_factor(worldPos, light->direction, 10.0f); - - /* Combine */ - float brightness = light->ambientIntensity * ao; - brightness += diffuse * light->intensity * shadowFactor * - (1.0f - light->ambientIntensity) * ao; - brightness += specular * light->intensity * shadowFactor * 0.6f; - - brightness = fminf(1.0f, brightness); - - int r = (int)(base.r * brightness); - int g = (int)(base.g * brightness); - int b = (int)(base.b * brightness); - - if (r > 255) r = 255; - if (g > 255) g = 255; - if (b > 255) b = 255; - - return (SuicColor){r, g, b, base.a}; -} diff --git a/game/suic_lighting.h b/game/suic_lighting.h deleted file mode 100644 index 680a9c9..0000000 --- a/game/suic_lighting.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include "suic_math.h" -#include - -/** - * Lighting system API. - * Supports directional lighting, shadows, ambient occlusion, and Phong shading. - */ - -typedef struct SuicColor { - uint8_t r, g, b, a; -} SuicColor; - -typedef struct SuicDirectionalLight { - SuicVec3 direction; - SuicVec3 color; - float intensity; - float ambientIntensity; - float shadowBias; - float shadowIntensity; -} SuicDirectionalLight; - -/* Light creation */ -SuicDirectionalLight suic_light_create(float dir_x, float dir_y, float dir_z, - float intensity, float ambient); -SuicDirectionalLight suic_light_create_default(void); - -/* Basic lighting */ -SuicColor suic_light_apply(SuicColor base, SuicVec3 normal, SuicDirectionalLight* light); - -/* Lighting with shadows */ -SuicColor suic_light_apply_shadows(SuicColor base, SuicVec3 normal, SuicVec3 worldPos, - SuicDirectionalLight* light); - -/* Phong lighting (with specular) */ -SuicColor suic_light_apply_phong(SuicColor base, SuicVec3 normal, SuicVec3 worldPos, - SuicVec3 camPos, SuicDirectionalLight* light); - -/* Shadow calculations */ -float suic_shadow_factor(SuicVec3 worldPos, SuicVec3 lightDir, float maxDist); -float suic_shadow_temporal(SuicVec3 worldPos, float timePhase); - -/* Ambient occlusion */ -float suic_ao_calculate(SuicVec3 worldPos, SuicVec3 normal); diff --git a/game/suic_lighting.o b/game/suic_lighting.o deleted file mode 100644 index ae4e273..0000000 Binary files a/game/suic_lighting.o and /dev/null differ diff --git a/game/suic_math.c b/game/suic_math.c deleted file mode 100644 index 1325301..0000000 --- a/game/suic_math.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "suic_math.h" -#include - -SuicVec3 suic_v3(float x, float y, float z) { - return (SuicVec3){x, y, z}; -} - -SuicVec3 suic_v3_add(SuicVec3 a, SuicVec3 b) { - return suic_v3(a.x + b.x, a.y + b.y, a.z + b.z); -} - -SuicVec3 suic_v3_sub(SuicVec3 a, SuicVec3 b) { - return suic_v3(a.x - b.x, a.y - b.y, a.z - b.z); -} - -SuicVec3 suic_v3_mul(SuicVec3 a, float s) { - return suic_v3(a.x * s, a.y * s, a.z * s); -} - -float suic_v3_dot(SuicVec3 a, SuicVec3 b) { - return a.x * b.x + a.y * b.y + a.z * b.z; -} - -float suic_v3_len(SuicVec3 a) { - return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z); -} - -SuicVec3 suic_v3_norm(SuicVec3 a) { - float l = suic_v3_len(a); - if (l <= 1e-6f) return suic_v3(0, 0, 1); - return suic_v3(a.x / l, a.y / l, a.z / l); -} - -SuicVec3 suic_v3_normalize(SuicVec3 v) { - float len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); - if (len < 0.0001f) return suic_v3(0, 1, 0); - return suic_v3(v.x / len, v.y / len, v.z / len); -} diff --git a/game/suic_math.h b/game/suic_math.h deleted file mode 100644 index e6609d6..0000000 --- a/game/suic_math.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#include - -/** - * Vector3 math utilities for game code. - * Designed to be callable from .sui via transpiler bindings. - */ - -typedef struct SuicVec3 { - float x, y, z; -} SuicVec3; - -/* Construction */ -SuicVec3 suic_v3(float x, float y, float z); - -/* Arithmetic */ -SuicVec3 suic_v3_add(SuicVec3 a, SuicVec3 b); -SuicVec3 suic_v3_sub(SuicVec3 a, SuicVec3 b); -SuicVec3 suic_v3_mul(SuicVec3 a, float s); - -/* Dot/Length/Normalize */ -float suic_v3_dot(SuicVec3 a, SuicVec3 b); -float suic_v3_len(SuicVec3 a); -SuicVec3 suic_v3_norm(SuicVec3 a); -SuicVec3 suic_v3_normalize(SuicVec3 v); diff --git a/game/suic_math.o b/game/suic_math.o deleted file mode 100644 index 1dd8bc9..0000000 Binary files a/game/suic_math.o and /dev/null differ diff --git a/game/suic_net.c b/game/suic_net.c deleted file mode 100644 index 0f170ff..0000000 --- a/game/suic_net.c +++ /dev/null @@ -1,337 +0,0 @@ -#include "suic_net.h" -#include -#include - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#include -#pragma comment(lib, "ws2_32.lib") -typedef int socklen_t; -#define CLOSESOCK closesocket -#else -#include -#include -#include -#include -#include -#define CLOSESOCK close -#endif - -#define PROTOCOL_VERSION 67 - -/* ===== MESSAGE TYPES ===== */ - -#pragma pack(push, 1) -typedef enum MsgType : uint8_t { - MSG_HELLO = 1, - MSG_WELCOME = 2, - MSG_INPUT = 3, - MSG_SNAPSHOT = 4, - MSG_SHOOT = 5, - MSG_ITEMS = 6, - MSG_ROOM_STATE = 7 -} MsgType; - -typedef struct MsgHello { - uint8_t type; - uint32_t protocol; - char username[SUIC_NET_USERNAME_MAX]; -} MsgHello; - -typedef struct MsgWelcome { - uint8_t type; - uint8_t playerId; - uint32_t serverTick; -} MsgWelcome; - -typedef struct MsgInput { - uint8_t type; - uint8_t playerId; - uint32_t clientTick; - float moveX, moveZ, yaw, pitch; - uint8_t buttons; -} MsgInput; - -typedef struct MsgShoot { - uint8_t type; - uint8_t playerId; - uint32_t clientTick; -} MsgShoot; - -typedef struct PlayerStateNet { - uint8_t id, alive; - int16_t hp; - float x, y, z, yaw, pitch; - uint8_t weapon; - int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; - int16_t reloadTimeLeft; - char username[SUIC_NET_USERNAME_MAX]; -} PlayerStateNet; - -typedef struct MsgSnapshot { - uint8_t type; - uint32_t serverTick; - uint8_t count; - PlayerStateNet p[SUIC_NET_MAX_PLAYERS]; -} MsgSnapshot; - -typedef struct ItemNet { - uint16_t id; - uint8_t type; - int16_t qty; - float x, y, z; -} ItemNet; - -typedef struct MsgItems { - uint8_t type; - uint32_t serverTick; - uint8_t count; - ItemNet items[SUIC_NET_MAX_ITEMS]; -} MsgItems; - -typedef struct MsgRoomState { - uint8_t type; - uint8_t state; - float countdownRemaining; - uint8_t winnerId; - char winnerName[SUIC_NET_USERNAME_MAX]; -} MsgRoomState; -#pragma pack(pop) - -/* ===== LOCAL PLAYER DATA ===== */ - -typedef struct RemotePlayer { - int present, alive, hp; - float x, y, z; - float prevX, prevY, prevZ; - float yaw, pitch; - uint8_t weapon; - int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; - int16_t reloadTimeLeft; - char username[SUIC_NET_USERNAME_MAX]; -} RemotePlayer; - -typedef struct WorldItem { - int present; - uint16_t id; - uint8_t type; - int16_t qty; - float x, y, z; -} WorldItem; - -/* ===== STATIC STATE ===== */ - -static int g_sock = -1; -static struct sockaddr_in g_server = {0}; -static uint8_t g_myId = 255; -static RemotePlayer g_players[SUIC_NET_MAX_PLAYERS] = {0}; -static WorldItem g_items[SUIC_NET_MAX_ITEMS] = {0}; -static int g_roomState = 0; -static float g_countdown = 0.0f; -static char g_winnerName[SUIC_NET_USERNAME_MAX] = {0}; - -static void set_nonblocking(int sock) { -#ifdef _WIN32 - u_long mode = 1; - ioctlsocket(sock, FIONBIO, &mode); -#else - int flags = fcntl(sock, F_GETFL, 0); - fcntl(sock, F_SETFL, flags | O_NONBLOCK); -#endif -} - -/* ===== PUBLIC API ===== */ - -int suic_net_init(const char* server_ip, int port) { -#ifdef _WIN32 - WSADATA wsa; - WSAStartup(MAKEWORD(2, 2), &wsa); -#endif - - g_sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (g_sock < 0) { - perror("socket"); - return -1; - } - set_nonblocking(g_sock); - - memset(&g_server, 0, sizeof(g_server)); - g_server.sin_family = AF_INET; - g_server.sin_port = htons(port); - inet_pton(AF_INET, server_ip, &g_server.sin_addr); - - g_myId = 255; - memset(g_players, 0, sizeof(g_players)); - memset(g_items, 0, sizeof(g_items)); - - return 0; -} - -void suic_net_shutdown(void) { - if (g_sock >= 0) { - CLOSESOCK(g_sock); - g_sock = -1; - } -#ifdef _WIN32 - WSACleanup(); -#endif -} - -bool suic_net_is_connected(void) { - return g_myId != 255; -} - -void suic_net_send_hello(const char* username) { - MsgHello hello = {0}; - hello.type = MSG_HELLO; - hello.protocol = PROTOCOL_VERSION; - strncpy(hello.username, username, SUIC_NET_USERNAME_MAX - 1); - sendto(g_sock, (const char*)&hello, (int)sizeof(hello), 0, - (const struct sockaddr*)&g_server, sizeof(g_server)); -} - -void suic_net_send_input(uint8_t playerId, uint32_t clientTick, - float moveX, float moveZ, float yaw, float pitch, - uint8_t buttons) { - MsgInput in = {0}; - in.type = MSG_INPUT; - in.playerId = playerId; - in.clientTick = clientTick; - in.moveX = moveX; - in.moveZ = moveZ; - in.yaw = yaw; - in.pitch = pitch; - in.buttons = buttons; - sendto(g_sock, (const char*)&in, (int)sizeof(in), 0, - (const struct sockaddr*)&g_server, sizeof(g_server)); -} - -void suic_net_send_shoot(uint8_t playerId, uint32_t clientTick) { - MsgShoot msg = {0}; - msg.type = MSG_SHOOT; - msg.playerId = playerId; - msg.clientTick = clientTick; - sendto(g_sock, (const char*)&msg, (int)sizeof(msg), 0, - (const struct sockaddr*)&g_server, sizeof(g_server)); -} - -bool suic_net_poll(void) { - bool receivedAny = false; - - for (;;) { - uint8_t buf[1400]; - struct sockaddr_in from = {0}; - socklen_t fromLen = sizeof(from); - int n = (int)recvfrom(g_sock, (char*)buf, (int)sizeof(buf), 0, - (struct sockaddr*)&from, &fromLen); - if (n <= 0) { -#ifdef _WIN32 - if (WSAGetLastError() == WSAEWOULDBLOCK) break; -#else - if (errno == EWOULDBLOCK || errno == EAGAIN) break; -#endif - break; - } - - receivedAny = true; - uint8_t type = buf[0]; - - if (type == MSG_WELCOME && n >= (int)sizeof(MsgWelcome)) { - MsgWelcome* w = (MsgWelcome*)buf; - g_myId = w->playerId; - } else if (type == MSG_SNAPSHOT && n >= (int)sizeof(MsgSnapshot)) { - MsgSnapshot* s = (MsgSnapshot*)buf; - for (int i = 0; i < SUIC_NET_MAX_PLAYERS; i++) - g_players[i].present = 0; - - for (int i = 0; i < (int)s->count && i < SUIC_NET_MAX_PLAYERS; i++) { - PlayerStateNet* ps = &s->p[i]; - if (ps->id >= SUIC_NET_MAX_PLAYERS) continue; - - RemotePlayer* p = &g_players[ps->id]; - p->prevX = p->x; - p->prevY = p->y; - p->prevZ = p->z; - p->present = 1; - p->alive = ps->alive; - p->hp = ps->hp; - p->x = ps->x; - p->y = ps->y; - p->z = ps->z; - p->yaw = ps->yaw; - p->pitch = ps->pitch; - p->weapon = ps->weapon; - p->pistolMag = ps->pistolMag; - p->rifleMag = ps->rifleMag; - p->pistolAmmo = ps->pistolAmmo; - p->rifleAmmo = ps->rifleAmmo; - p->medkits = ps->medkits; - p->reloadTimeLeft = ps->reloadTimeLeft; - memset(p->username, 0, SUIC_NET_USERNAME_MAX); - strncpy(p->username, ps->username, SUIC_NET_USERNAME_MAX - 1); - } - } else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) { - MsgItems* m = (MsgItems*)buf; - for (int i = 0; i < SUIC_NET_MAX_ITEMS; i++) - g_items[i].present = 0; - - for (int i = 0; i < (int)m->count && i < SUIC_NET_MAX_ITEMS; i++) { - g_items[i].present = 1; - g_items[i].id = m->items[i].id; - g_items[i].type = m->items[i].type; - g_items[i].qty = m->items[i].qty; - g_items[i].x = m->items[i].x; - g_items[i].y = m->items[i].y; - g_items[i].z = m->items[i].z; - } - } else if (type == MSG_ROOM_STATE && n >= (int)sizeof(MsgRoomState)) { - const MsgRoomState* rs = (const MsgRoomState*)buf; - g_roomState = rs->state; - g_countdown = rs->countdownRemaining; - memset(g_winnerName, 0, SUIC_NET_USERNAME_MAX); - if (rs->winnerId < 255) { - strncpy(g_winnerName, rs->winnerName, SUIC_NET_USERNAME_MAX - 1); - } - } - } - - return receivedAny; -} - -uint8_t suic_net_get_my_id(void) { return g_myId; } - -/* Player accessors */ -bool suic_net_player_present(int i) { return i >= 0 && i < SUIC_NET_MAX_PLAYERS && g_players[i].present; } -bool suic_net_player_alive(int i) { return suic_net_player_present(i) && g_players[i].alive; } -int suic_net_player_hp(int i) { return suic_net_player_present(i) ? g_players[i].hp : 0; } -float suic_net_player_x(int i) { return suic_net_player_present(i) ? g_players[i].x : 0; } -float suic_net_player_y(int i) { return suic_net_player_present(i) ? g_players[i].y : 0; } -float suic_net_player_z(int i) { return suic_net_player_present(i) ? g_players[i].z : 0; } -float suic_net_player_prev_x(int i) { return suic_net_player_present(i) ? g_players[i].prevX : 0; } -float suic_net_player_prev_y(int i) { return suic_net_player_present(i) ? g_players[i].prevY : 0; } -float suic_net_player_prev_z(int i) { return suic_net_player_present(i) ? g_players[i].prevZ : 0; } -float suic_net_player_yaw(int i) { return suic_net_player_present(i) ? g_players[i].yaw : 0; } -float suic_net_player_pitch(int i) { return suic_net_player_present(i) ? g_players[i].pitch : 0; } -uint8_t suic_net_player_weapon(int i) { return suic_net_player_present(i) ? g_players[i].weapon : 0; } -int16_t suic_net_player_pistol_mag(int i) { return suic_net_player_present(i) ? g_players[i].pistolMag : 0; } -int16_t suic_net_player_rifle_mag(int i) { return suic_net_player_present(i) ? g_players[i].rifleMag : 0; } -int16_t suic_net_player_pistol_ammo(int i) { return suic_net_player_present(i) ? g_players[i].pistolAmmo : 0; } -int16_t suic_net_player_rifle_ammo(int i) { return suic_net_player_present(i) ? g_players[i].rifleAmmo : 0; } -int16_t suic_net_player_medkits(int i) { return suic_net_player_present(i) ? g_players[i].medkits : 0; } -int16_t suic_net_player_reload_time(int i) { return suic_net_player_present(i) ? g_players[i].reloadTimeLeft : 0; } -const char* suic_net_player_username(int i) { return suic_net_player_present(i) ? g_players[i].username : ""; } - -/* Item accessors */ -bool suic_net_item_present(int i) { return i >= 0 && i < SUIC_NET_MAX_ITEMS && g_items[i].present; } -uint16_t suic_net_item_id(int i) { return suic_net_item_present(i) ? g_items[i].id : 0; } -uint8_t suic_net_item_type(int i) { return suic_net_item_present(i) ? g_items[i].type : 0; } -int16_t suic_net_item_qty(int i) { return suic_net_item_present(i) ? g_items[i].qty : 0; } -float suic_net_item_x(int i) { return suic_net_item_present(i) ? g_items[i].x : 0; } -float suic_net_item_y(int i) { return suic_net_item_present(i) ? g_items[i].y : 0; } -float suic_net_item_z(int i) { return suic_net_item_present(i) ? g_items[i].z : 0; } - -/* Room state */ -int suic_net_room_state(void) { return g_roomState; } -float suic_net_countdown(void) { return g_countdown; } -const char* suic_net_winner_name(void) { return g_winnerName; } diff --git a/game/suic_net.h b/game/suic_net.h deleted file mode 100644 index cf7cbed..0000000 --- a/game/suic_net.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once -#include -#include - -/** - * Network protocol API for game client. - * Abstracts UDP socket communication with the game server. - */ - -#define SUIC_NET_MAX_PLAYERS 16 -#define SUIC_NET_USERNAME_MAX 16 -#define SUIC_NET_MAX_ITEMS 64 - -/* Connection lifecycle */ -int suic_net_init(const char* server_ip, int port); -void suic_net_shutdown(void); -bool suic_net_is_connected(void); - -/* Sending */ -void suic_net_send_hello(const char* username); -void suic_net_send_input(uint8_t playerId, uint32_t clientTick, - float moveX, float moveZ, float yaw, float pitch, - uint8_t buttons); -void suic_net_send_shoot(uint8_t playerId, uint32_t clientTick); - -/* Receiving (poll-based) */ -bool suic_net_poll(void); -uint8_t suic_net_get_my_id(void); - -/* Player accessors */ -bool suic_net_player_present(int index); -bool suic_net_player_alive(int index); -int suic_net_player_hp(int index); -float suic_net_player_x(int index); -float suic_net_player_y(int index); -float suic_net_player_z(int index); -float suic_net_player_prev_x(int index); -float suic_net_player_prev_y(int index); -float suic_net_player_prev_z(int index); -float suic_net_player_yaw(int index); -float suic_net_player_pitch(int index); -uint8_t suic_net_player_weapon(int index); -int16_t suic_net_player_pistol_mag(int index); -int16_t suic_net_player_rifle_mag(int index); -int16_t suic_net_player_pistol_ammo(int index); -int16_t suic_net_player_rifle_ammo(int index); -int16_t suic_net_player_medkits(int index); -int16_t suic_net_player_reload_time(int index); -const char* suic_net_player_username(int index); - -/* Item accessors */ -bool suic_net_item_present(int index); -uint16_t suic_net_item_id(int index); -uint8_t suic_net_item_type(int index); -int16_t suic_net_item_qty(int index); -float suic_net_item_x(int index); -float suic_net_item_y(int index); -float suic_net_item_z(int index); - -/* Room state */ -int suic_net_room_state(void); -float suic_net_countdown(void); -const char* suic_net_winner_name(void); - -/* Button flags */ -#define SUIC_BTN_RELOAD (1u << 0) -#define SUIC_BTN_SWITCH_PISTOL (1u << 1) -#define SUIC_BTN_SWITCH_RIFLE (1u << 2) -#define SUIC_BTN_PICK (1u << 3) -#define SUIC_BTN_USE_MEDKIT (1u << 4) -#define SUIC_BTN_JUMP (1u << 5) - -/* Weapon types */ -#define SUIC_WEAPON_PISTOL 0 -#define SUIC_WEAPON_RIFLE 1 - -/* Item types */ -#define SUIC_ITEM_NONE 0 -#define SUIC_ITEM_MEDKIT 1 -#define SUIC_ITEM_AMMO_PISTOL 2 -#define SUIC_ITEM_AMMO_RIFLE 3 diff --git a/game/suic_net.o b/game/suic_net.o deleted file mode 100644 index 22f7ec8..0000000 Binary files a/game/suic_net.o and /dev/null differ diff --git a/game/suic_terrain.c b/game/suic_terrain.c deleted file mode 100644 index 1337d8e..0000000 --- a/game/suic_terrain.c +++ /dev/null @@ -1,283 +0,0 @@ -#include "suic_terrain.h" -#include "raylib.h" -#include -#include -#include - -/* ===== SIMPLEX NOISE IMPLEMENTATION ===== */ - -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, - 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, - 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, - 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, - 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, - 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, - 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, - 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, - 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, - 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, - 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, - 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, - 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, - 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, - 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, - 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, - 180, 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, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, - 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, - 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, - 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, - 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, - 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, - 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, - 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, - 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, - 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, - 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, - 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, - 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, - 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, - 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, - 156, 180}; - -static float grad2(int hash, float x, float y) { - int h = hash & 7; - float u = h < 4 ? x : y; - float v = h < 4 ? y : x; - return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f * v : 2.0f * v); -} - -float suic_simplex_noise_2d(float x, float y) { - const float F2 = 0.366025403f; - const float G2 = 0.211324865f; - float s = (x + y) * F2; - int i = (int)floorf(x + s); - int j = (int)floorf(y + s); - float t = (i + j) * G2; - float X0 = i - t, Y0 = j - t; - float x0 = x - X0, y0 = y - Y0; - int i1 = (x0 > y0) ? 1 : 0; - int j1 = (x0 > y0) ? 0 : 1; - float x1 = x0 - i1 + G2, y1 = y0 - j1 + G2; - float x2 = x0 - 1.0f + 2.0f * G2, y2 = y0 - 1.0f + 2.0f * G2; - int ii = i & 255, jj = j & 255; - float n0 = 0.0f, n1 = 0.0f, n2 = 0.0f; - float t0 = 0.5f - x0 * x0 - y0 * y0; - if (t0 >= 0.0f) { - t0 *= t0; - n0 = t0 * t0 * grad2(perm[ii + perm[jj]], x0, y0); - } - float t1 = 0.5f - x1 * x1 - y1 * y1; - if (t1 >= 0.0f) { - t1 *= t1; - n1 = t1 * t1 * grad2(perm[ii + i1 + perm[jj + j1]], x1, y1); - } - float t2 = 0.5f - x2 * x2 - y2 * y2; - if (t2 >= 0.0f) { - t2 *= t2; - n2 = t2 * t2 * grad2(perm[ii + 1 + perm[jj + 1]], x2, y2); - } - return 45.0f * (n0 + n1 + n2); -} - -float suic_fbm_noise(float x, float y, int octaves) { - float value = 0.0f, amplitude = 1.0f, frequency = 1.0f, max_value = 0.0f; - for (int i = 0; i < octaves; i++) { - value += suic_simplex_noise_2d(x * frequency, y * frequency) * amplitude; - max_value += amplitude; - amplitude *= 0.5f; - frequency *= 2.0f; - } - return value / max_value; -} - -float suic_terrain_height(float x, float z) { - float height = suic_fbm_noise(x * 0.05f, z * 0.05f, 2); - height += suic_fbm_noise(x * 0.01f, z * 0.01f, 2) * 1.5f; - return height * 4.0f + 2.0f; -} - -/* ===== TERRAIN MESH & MODEL ===== */ - -static Mesh g_terrainMesh = {0}; -static Model g_terrainModel = {0}; -static SuicTerrainShader g_terrainShader = {0}; -static bool g_terrainInitialized = false; - -static Mesh generate_terrain_mesh(void) { - int size = SUIC_TERRAIN_SIZE; - Mesh mesh = {0}; - - int vertexCount = size * size; - int triangleCount = (size - 1) * (size - 1) * 2; - - mesh.vertexCount = vertexCount; - mesh.triangleCount = triangleCount; - - mesh.vertices = (float *)MemAlloc(vertexCount * 3 * sizeof(float)); - mesh.texcoords = (float *)MemAlloc(vertexCount * 2 * sizeof(float)); - mesh.normals = (float *)MemAlloc(vertexCount * 3 * sizeof(float)); - mesh.indices = (unsigned short *)MemAlloc(triangleCount * 3 * sizeof(unsigned short)); - - /* Generate vertices */ - 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) * SUIC_TERRAIN_SCALE; - float wz = ((float)z - size / 2.0f) * SUIC_TERRAIN_SCALE; - float wy = suic_terrain_height(wx, wz); - - mesh.vertices[idx * 3 + 0] = wx; - mesh.vertices[idx * 3 + 1] = wy; - mesh.vertices[idx * 3 + 2] = wz; - - mesh.texcoords[idx * 2 + 0] = (float)x / (float)(size - 1); - mesh.texcoords[idx * 2 + 1] = (float)z / (float)(size - 1); - } - } - - /* Generate indices */ - int triIdx = 0; - for (int z = 0; z < size - 1; z++) { - for (int x = 0; x < size - 1; x++) { - int i0 = z * size + x; - int i1 = z * size + (x + 1); - int i2 = (z + 1) * size + x; - int i3 = (z + 1) * size + (x + 1); - - mesh.indices[triIdx * 3 + 0] = i0; - mesh.indices[triIdx * 3 + 1] = i2; - mesh.indices[triIdx * 3 + 2] = i1; - triIdx++; - - mesh.indices[triIdx * 3 + 0] = i1; - mesh.indices[triIdx * 3 + 1] = i2; - mesh.indices[triIdx * 3 + 2] = i3; - triIdx++; - } - } - - /* Calculate normals using height field gradient */ - for (int z = 0; z < size; z++) { - for (int x = 0; x < size; x++) { - int idx = z * size + x; - - 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]; - - float dh_dx = (h_right - h_left) / (2.0f * SUIC_TERRAIN_SCALE); - float dh_dz = (h_down - h_up) / (2.0f * SUIC_TERRAIN_SCALE); - - float nx = -dh_dx; - float ny = 1.0f; - float nz = -dh_dz; - - 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; - } - } - } - - UploadMesh(&mesh, false); - return mesh; -} - -void suic_terrain_init(void) { - if (g_terrainInitialized) return; - - g_terrainMesh = generate_terrain_mesh(); - g_terrainModel = LoadModelFromMesh(g_terrainMesh); - g_terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = (Color){80, 140, 70, 255}; - - g_terrainShader = suic_terrain_shader_load(); - if (g_terrainShader.shaderId != 0) { - g_terrainModel.materials[0].shader.id = g_terrainShader.shaderId; - } - - g_terrainInitialized = true; -} - -void suic_terrain_draw(SuicVec3 cameraPos) { - if (!g_terrainInitialized) return; - - /* Set shader uniforms if shader is loaded */ - if (g_terrainShader.shaderId != 0) { - SuicVec3 lightDir = suic_v3_norm(suic_v3(-0.8f, -1.0f, -0.6f)); - SuicVec3 lightColor = suic_v3(1.0f, 1.0f, 1.0f); - suic_terrain_shader_set_uniforms(&g_terrainShader, cameraPos, lightDir, lightColor, 1.2f, 0.3f); - } - - g_terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = (Color){80, 140, 70, 255}; - DrawModel(g_terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE); -} - -void suic_terrain_cleanup(void) { - if (!g_terrainInitialized) return; - UnloadModel(g_terrainModel); - suic_terrain_shader_unload(&g_terrainShader); - g_terrainInitialized = false; -} - -/* ===== SHADER MANAGEMENT ===== */ - -SuicTerrainShader suic_terrain_shader_load(void) { - SuicTerrainShader ts = {0}; - Shader shader = LoadShader("terrain.vs", "terrain.fs"); - ts.shaderId = shader.id; - - if (shader.id != 0) { - ts.locViewPos = GetShaderLocation(shader, "viewPos"); - ts.locLightDir = GetShaderLocation(shader, "lightDir"); - ts.locLightColor = GetShaderLocation(shader, "lightColor"); - ts.locLightIntensity = GetShaderLocation(shader, "lightIntensity"); - ts.locAmbientIntensity = GetShaderLocation(shader, "ambientIntensity"); - ts.locTerrainColor = GetShaderLocation(shader, "terrainColor"); - } - - return ts; -} - -void suic_terrain_shader_unload(SuicTerrainShader* ts) { - if (ts && ts->shaderId != 0) { - Shader s = {ts->shaderId, NULL}; - UnloadShader(s); - ts->shaderId = 0; - } -} - -void suic_terrain_shader_set_uniforms(SuicTerrainShader* ts, SuicVec3 viewPos, - SuicVec3 lightDir, SuicVec3 lightColor, - float intensity, float ambient) { - if (!ts || ts->shaderId == 0) return; - - Shader shader = {ts->shaderId, NULL}; - float viewPosArray[3] = {viewPos.x, viewPos.y, viewPos.z}; - float lightDirArray[3] = {-lightDir.x, -lightDir.y, -lightDir.z}; - float lightColorArray[3] = {lightColor.x, lightColor.y, lightColor.z}; - float terrainColorArray[3] = {80.0f / 255.0f, 140.0f / 255.0f, 70.0f / 255.0f}; - - SetShaderValue(shader, ts->locViewPos, viewPosArray, SHADER_UNIFORM_VEC3); - SetShaderValue(shader, ts->locLightDir, lightDirArray, SHADER_UNIFORM_VEC3); - SetShaderValue(shader, ts->locLightColor, lightColorArray, SHADER_UNIFORM_VEC3); - SetShaderValue(shader, ts->locLightIntensity, &intensity, SHADER_UNIFORM_FLOAT); - SetShaderValue(shader, ts->locAmbientIntensity, &ambient, SHADER_UNIFORM_FLOAT); - SetShaderValue(shader, ts->locTerrainColor, terrainColorArray, SHADER_UNIFORM_VEC3); -} diff --git a/game/suic_terrain.h b/game/suic_terrain.h deleted file mode 100644 index 274b9fc..0000000 --- a/game/suic_terrain.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once -#include "suic_math.h" -#include - -/** - * Terrain generation and rendering API. - * Uses simplex noise for procedural heightmap generation. - */ - -/* Terrain constants */ -#define SUIC_TERRAIN_SIZE 256 -#define SUIC_TERRAIN_SCALE 1.0f -#define SUIC_TERRAIN_MIN (-SUIC_TERRAIN_SIZE * SUIC_TERRAIN_SCALE / 2.0f) -#define SUIC_TERRAIN_MAX (SUIC_TERRAIN_SIZE * SUIC_TERRAIN_SCALE / 2.0f) - -/* Noise generation */ -float suic_simplex_noise_2d(float x, float y); -float suic_fbm_noise(float x, float y, int octaves); - -/* Terrain height query */ -float suic_terrain_height(float x, float z); - -/* Terrain lifecycle */ -void suic_terrain_init(void); -void suic_terrain_draw(SuicVec3 cameraPos); -void suic_terrain_cleanup(void); - -/* Shader management */ -typedef struct SuicTerrainShader { - unsigned int shaderId; - int locViewPos; - int locLightDir; - int locLightColor; - int locLightIntensity; - int locAmbientIntensity; - int locTerrainColor; -} SuicTerrainShader; - -SuicTerrainShader suic_terrain_shader_load(void); -void suic_terrain_shader_unload(SuicTerrainShader* ts); -void suic_terrain_shader_set_uniforms(SuicTerrainShader* ts, SuicVec3 viewPos, - SuicVec3 lightDir, SuicVec3 lightColor, - float intensity, float ambient); diff --git a/game/suic_terrain.o b/game/suic_terrain.o deleted file mode 100644 index bbaa7ea..0000000 Binary files a/game/suic_terrain.o and /dev/null differ diff --git a/game/suic_ui.c b/game/suic_ui.c deleted file mode 100644 index c956d62..0000000 --- a/game/suic_ui.c +++ /dev/null @@ -1,163 +0,0 @@ -#include "suic_ui.h" -#include "suic_net.h" -#include "raylib.h" -#include -#include -#include - -/* ===== USERNAME PROMPT ===== */ - -void suic_ui_username_prompt(char* outName, int maxLen) { - memset(outName, 0, maxLen); - while (!WindowShouldClose()) { - int ch = GetCharPressed(); - while (ch > 0) { - int len = (int)strlen(outName); - if (ch >= 32 && ch <= 126) { - if (len < maxLen - 1) { - outName[len] = (char)ch; - outName[len + 1] = '\0'; - } - } - ch = GetCharPressed(); - } - if (IsKeyPressed(KEY_BACKSPACE)) { - int len = (int)strlen(outName); - if (len > 0) outName[len - 1] = '\0'; - } - if (IsKeyPressed(KEY_ENTER) && strlen(outName) > 0) - return; - - BeginDrawing(); - ClearBackground((Color){20, 24, 32, 255}); - DrawText("Enter username (press ENTER):", 60, 80, 28, RAYWHITE); - DrawRectangle(60, 130, 420, 48, (Color){40, 48, 64, 255}); - DrawRectangleLines(60, 130, 420, 48, (Color){120, 140, 170, 255}); - DrawText(outName[0] ? outName : "_", 72, 142, 24, (Color){230, 230, 240, 255}); - EndDrawing(); - } -} - -/* ===== HUD ===== */ - -void suic_ui_draw_hud(int hp, int weapon, int mag, int reserve, - int medkits, int reloadTimeLeft) { - int sw = GetScreenWidth(); - int sh = GetScreenHeight(); - - /* Health panel */ - DrawRectangle(10, 10, 280, 110, (Color){0, 0, 0, 120}); - DrawText("HP", 20, 20, 20, RAYWHITE); - int healthBarWidth = (hp * 220) / 100; - Color healthColor = (hp > 60) ? (Color){80, 255, 80, 120} - : (hp > 30) ? (Color){255, 200, 80, 120} - : (Color){255, 80, 80, 120}; - DrawRectangle(20, 45, 220, 20, (Color){40, 40, 40, 255}); - DrawRectangle(20, 45, healthBarWidth, 20, healthColor); - DrawText(TextFormat("%d", hp), 250, 47, 18, RAYWHITE); - DrawText(TextFormat("Medkits: %d (H to use)", medkits), 20, 75, 18, - (medkits > 0) ? (Color){120, 255, 120, 120} : (Color){120, 120, 120, 120}); - DrawText("1=Pistol 2=Rifle R=Reload F=Pick SPACE=Jump", 20, 95, 12, - (Color){150, 150, 150, 150}); - - /* Weapon panel */ - const char* weaponName = (weapon == SUIC_WEAPON_PISTOL) ? "PISTOL" : "RIFLE"; - Color weaponColor = (weapon == SUIC_WEAPON_PISTOL) - ? (Color){100, 200, 255, 255} - : (Color){255, 150, 100, 255}; - - DrawRectangle(sw - 260, sh - 120, 250, 110, (Color){0, 0, 0, 180}); - DrawText(weaponName, sw - 250, sh - 110, 28, weaponColor); - DrawText(TextFormat("%d", mag), sw - 250, sh - 75, 40, RAYWHITE); - DrawText(TextFormat("/ %d", reserve), sw - 140, sh - 65, 24, (Color){180, 180, 180, 255}); - if (reloadTimeLeft > 0) - DrawText("RELOADING...", sw - 250, sh - 30, 20, (Color){255, 200, 80, 255}); - else if (mag == 0) - DrawText("RELOAD!", sw - 250, sh - 30, 20, (Color){255, 80, 80, 255}); -} - -/* ===== CROSSHAIR ===== */ - -void suic_ui_draw_crosshair(int screenW, int screenH, float spread, bool scoped) { - int cx = screenW / 2, cy = screenH / 2; - int gap = scoped ? 2 : 6 + (int)spread; - int len = scoped ? 5 : 10; - int 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}); -} - -/* ===== ROOM STATE ===== */ - -void suic_ui_draw_room_state(int screenW, int screenH, int state, - float countdown, const char* winnerName) { - if (state == 0) { /* Waiting */ - DrawRectangle(screenW / 2 - 150, screenH / 2 - 50, 350, 100, (Color){0, 0, 0, 200}); - DrawText("WAITING FOR PLAYERS", screenW / 2 - 120, screenH / 2 - 30, 24, RAYWHITE); - DrawText("Need at least 2 players", screenW / 2 - 100, screenH / 2 - 5, 18, - (Color){200, 200, 200, 255}); - } else if (state == 1) { /* Counting down */ - DrawRectangle(screenW / 2 - 150, screenH / 2 - 50, 350, 100, (Color){0, 0, 0, 200}); - DrawText("GAME STARTING SOON", screenW / 2 - 120, screenH / 2 - 30, 24, - (Color){255, 255, 80, 255}); - DrawText(TextFormat("%.1f seconds", countdown), screenW / 2 - 60, - screenH / 2 - 5, 20, RAYWHITE); - } else if (state == 3) { /* Finished */ - DrawRectangle(screenW / 2 - 200, screenH / 2 - 50, 450, 100, (Color){0, 0, 0, 200}); - DrawText("GAME FINISHED", screenW / 2 - 80, screenH / 2 - 30, 28, - (Color){255, 80, 80, 255}); - if (winnerName && winnerName[0]) { - DrawText(TextFormat("Winner: %s", winnerName), screenW / 2 - 100, screenH / 2 - 5, - 24, (Color){255, 255, 80, 255}); - } else { - DrawText("No winner", screenW / 2 - 50, screenH / 2 - 5, 24, RAYWHITE); - } - } -} - -/* ===== NAMEPLATES ===== */ - -void suic_ui_draw_nameplate(int screenW, int screenH, float worldX, float worldY, float worldZ, - const char* username, bool isLocalPlayer, - float camPosX, float camPosY, float camPosZ, - float camTargetX, float camTargetY, float camTargetZ) { - if (!username || !username[0]) return; - - Vector3 head = {worldX, worldY + 1.2f, worldZ}; - Vector3 camPos = {camPosX, camPosY, camPosZ}; - Vector3 camTarget = {camTargetX, camTargetY, camTargetZ}; - - /* Check if in front of camera */ - Vector3 camForward = {camTarget.x - camPos.x, camTarget.y - camPos.y, camTarget.z - camPos.z}; - float len = sqrtf(camForward.x * camForward.x + camForward.y * camForward.y + camForward.z * camForward.z); - if (len > 0) { - camForward.x /= len; - camForward.y /= len; - camForward.z /= len; - } - - Vector3 toHead = {head.x - camPos.x, head.y - camPos.y, head.z - camPos.z}; - float dot = camForward.x * toHead.x + camForward.y * toHead.y + camForward.z * toHead.z; - if (dot <= 0.0f) return; - - Camera3D cam = {0}; - cam.position = camPos; - cam.target = camTarget; - cam.up = (Vector3){0, 1, 0}; - cam.fovy = 75.0f; - cam.projection = CAMERA_PERSPECTIVE; - - Vector2 s = GetWorldToScreen(head, cam); - if (s.x < -200 || s.x > screenW + 200 || s.y < -200 || s.y > screenH + 200) - return; - - int fontSize = 18; - int w = MeasureText(username, fontSize); - Color tc = isLocalPlayer ? (Color){180, 230, 255, 255} : RAYWHITE; - DrawText(username, (int)(s.x - w / 2), (int)(s.y - fontSize), fontSize, tc); -} diff --git a/game/suic_ui.h b/game/suic_ui.h deleted file mode 100644 index 091c301..0000000 --- a/game/suic_ui.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include -#include - -/** - * UI rendering API for game client. - * HUD, crosshair, room state overlays, username prompt. - */ - -/* Username prompt (blocking) */ -void suic_ui_username_prompt(char* outName, int maxLen); - -/* HUD */ -void suic_ui_draw_hud(int hp, int weapon, int mag, int reserve, - int medkits, int reloadTimeLeft); - -/* Crosshair */ -void suic_ui_draw_crosshair(int screenW, int screenH, float spread, bool scoped); - -/* Room state overlay */ -void suic_ui_draw_room_state(int screenW, int screenH, int state, - float countdown, const char* winnerName); - -/* Nameplates */ -void suic_ui_draw_nameplate(int screenW, int screenH, float worldX, float worldY, float worldZ, - const char* username, bool isLocalPlayer, - float camPosX, float camPosY, float camPosZ, - float camTargetX, float camTargetY, float camTargetZ); diff --git a/game/suic_ui.o b/game/suic_ui.o deleted file mode 100644 index 3eecd50..0000000 Binary files a/game/suic_ui.o and /dev/null differ diff --git a/soup/server.c b/soup/server.c index b9e24ad..b341dca 100644 --- a/soup/server.c +++ b/soup/server.c @@ -4,6 +4,9 @@ #include #include +void* gc_alloc(const TypeInfo* type, size_t size); +void gc_init(void); +void gc_shutdown(void); // Helper for allocating arrays static void* suic_alloc_array(const TypeInfo* type, size_t elem_size, size_t len, void* init_data) { @@ -19,12 +22,12 @@ static void* suic_alloc_struct(const TypeInfo* type, size_t size, void* init_dat return ptr; } -void* gc_alloc(const TypeInfo* type, size_t size); -void gc_init(void); -void gc_shutdown(void); + + int suic_main(void); + int suic_main(void) { char* SERVER_HOST = suic_alloc_array(NULL, sizeof(char), 10, "127.0.0.1"); int SERVER_PORT = 8888; diff --git a/tests/arrays.c b/tests/arrays.c index 644f914..7e33cc5 100644 --- a/tests/arrays.c +++ b/tests/arrays.c @@ -51,7 +51,7 @@ int suic_main(void); int suic_main(void) { int* arr = suic_alloc_array(NULL, sizeof(int), 4, (int[]){1, 2, 3, 4}); - int x = arr[2]; + int x = arr[0]; return x; } diff --git a/tests/arrays.o b/tests/arrays.o index 6366f74..ec5d2dd 100755 Binary files a/tests/arrays.o and b/tests/arrays.o differ diff --git a/tests/arrays.sui b/tests/arrays.sui index 566c859..85d4c46 100644 --- a/tests/arrays.sui +++ b/tests/arrays.sui @@ -2,6 +2,6 @@ fn main() -> int do let arr = [1, 2, 3, 4]; # let empty = []; - let x = arr[1]; + let x = arr[0]; x end