suicmez/game/inc/suic_game_client.c
2025-12-20 15:09:29 +05:30

270 lines
11 KiB
C

#include "suic_game_client.h"
#include "raylib.h"
#include <math.h>
#include <string.h>
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; }