270 lines
11 KiB
C
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; }
|