#include "suic_game_client.h" #include "raylib.h" #include #include 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; 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(); } 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); } } 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 */ if (myId != 255) { 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); } 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("Offline", 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); } 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; }