suicmez/game/client.sui
2025-12-20 18:30:21 +05:30

455 lines
15 KiB
Text

struct SuicVec3
x: float,
y: float,
z: float,
end
struct SuicColor
r: u8,
g: u8,
b: u8,
a: u8,
end
struct SuicDirectionalLight
direction: SuicVec3,
color: SuicVec3,
intensity: float,
ambientIntensity: float,
shadowBias: float,
shadowIntensity: float,
end
# Constants
const PISTOL_FIRE_RATE = 4.0
const RIFLE_FIRE_RATE = 12.0
const RECOIL_RETURN = 18.0
const CROSS_RETURN = 14.0
const PISTOL_KICK_PITCH = 0.010
const PISTOL_KICK_YAW = 0.004
const RIFLE_KICK_PITCH = 0.018
const RIFLE_KICK_YAW = 0.010
const PISTOL_CROSS_KICK = 2.0
const RIFLE_CROSS_KICK = 4.0
const PISTOL_SPRAY_GROW = 0.4
const RIFLE_SPRAY_GROW = 1.2
const BURST_RESET_TIME = 0.18
const SUIC_BTN_RELOAD = 1
const SUIC_BTN_SWITCH_PISTOL = 2
const SUIC_BTN_SWITCH_RIFLE = 4
const SUIC_BTN_PICK = 8
const SUIC_BTN_USE_MEDKIT = 16
const SUIC_BTN_JUMP = 32
const SUIC_WEAPON_PISTOL = 0
const SUIC_WEAPON_RIFLE = 1
const SUIC_ITEM_MEDKIT = 1
const SUIC_ITEM_AMMO_PISTOL = 2
const SUIC_ITEM_AMMO_RIFLE = 3
const SUIC_TERRAIN_SIZE = 256.0
const SUIC_TERRAIN_SCALE = 1.0
const SUIC_TERRAIN_MIN = -256.0 * 1.0 / 2.0
const SUIC_TERRAIN_MAX = 256.0 * 1.0 / 2.0
const SUIC_NET_MAX_PLAYERS = 16
const SUIC_NET_USERNAME_MAX = 16
const SUIC_NET_MAX_ITEMS = 64
struct GameState
cam_pos: SuicVec3,
yaw: float,
pitch: float,
prev_body_pos: SuicVec3,
current_target_body: SuicVec3,
interp_timer: float,
recoil_yaw: float,
recoil_pitch: float,
cross_spread: float,
scoped: bool,
fire_cooldown: float,
shots_in_burst: int,
burst_reset_timer: float,
client_tick: int,
light: SuicDirectionalLight,
end
fn suic_game_init(game_state: *GameState, username: string, server_ip: string, port: int) -> () do
suic_terrain_init()
suic_net_init(server_ip, port)
suic_net_send_hello(username)
disable_cursor()
end
fn suic_game_shutdown() -> () do
suic_terrain_cleanup()
suic_net_shutdown()
end
fn suic_game_handle_input(game_state: *GameState, dt: float) -> () do
game_state.client_tick = game_state.client_tick + 1
# Cooldowns
game_state.fire_cooldown = game_state.fire_cooldown - dt
if game_state.fire_cooldown < 0.0 do
game_state.fire_cooldown = 0.0
end
game_state.burst_reset_timer = game_state.burst_reset_timer - dt
if game_state.burst_reset_timer <= 0.0 do
game_state.shots_in_burst = 0
end
# Recoil recovery
let k = 1.0 - expf(-RECOIL_RETURN * dt)
game_state.recoil_yaw = game_state.recoil_yaw + (0.0 - game_state.recoil_yaw) * k
game_state.recoil_pitch = game_state.recoil_pitch + (0.0 - game_state.recoil_pitch) * k
let k2 = 1.0 - expf(-CROSS_RETURN * dt)
game_state.cross_spread = game_state.cross_spread + (0.0 - game_state.cross_spread) * k2
if game_state.cross_spread < 0.01 do
game_state.cross_spread = 0.0
end
# Mouse look
let md = get_mouse_delta()
let sens = 0.0025
game_state.yaw = game_state.yaw - md.x * sens
game_state.pitch = game_state.pitch - md.y * sens
if game_state.pitch < -1.5 do
game_state.pitch = -1.5
end
if game_state.pitch > 1.5 do
game_state.pitch = 1.5
end
# Movement input
let mut move_x = 0.0
let mut move_z = 0.0
if is_key_down(65) do # KEY_A
move_x = move_x - 1.0
end
if is_key_down(68) do # KEY_D
move_x = move_x + 1.0
end
if is_key_down(87) do # KEY_W
move_z = move_z + 1.0
end
if is_key_down(83) do # KEY_S
move_z = move_z - 1.0
end
# Buttons
let mut buttons = 0
if is_key_pressed(82) do # KEY_R
buttons = buttons bitor SUIC_BTN_RELOAD
end
if is_key_pressed(49) do # KEY_ONE
buttons = buttons bitor SUIC_BTN_SWITCH_PISTOL
end
if is_key_pressed(50) do # KEY_TWO
buttons = buttons bitor SUIC_BTN_SWITCH_RIFLE
end
if is_key_pressed(70) do # KEY_F
buttons = buttons bitor SUIC_BTN_PICK
end
if is_key_pressed(72) do # KEY_H
buttons = buttons bitor SUIC_BTN_USE_MEDKIT
end
if is_key_pressed(32) do # KEY_SPACE
buttons = buttons bitor SUIC_BTN_JUMP
end
if is_key_pressed(90) do # KEY_Z
game_state.scoped = not game_state.scoped
end
let my_id = suic_net_get_my_id()
let view_yaw = game_state.yaw + game_state.recoil_yaw
let view_pitch = game_state.pitch + game_state.recoil_pitch
let mut clamped_view_pitch = view_pitch
if clamped_view_pitch < -1.5 do
clamped_view_pitch = -1.5
end
if clamped_view_pitch > 1.5 do
clamped_view_pitch = 1.5
end
if my_id != 255 do
suic_net_send_input(my_id, game_state.client_tick, move_x, move_z, view_yaw, clamped_view_pitch, buttons)
end
# Shooting
if my_id != 255 and is_mouse_button_down(0) and game_state.fire_cooldown <= 0.0 and suic_net_player_present(my_id) do
let wpn = suic_net_player_weapon(my_id)
let mut rate = RIFLE_FIRE_RATE
if wpn == SUIC_WEAPON_PISTOL do
rate = PISTOL_FIRE_RATE
end
game_state.fire_cooldown = 1.0 / rate
game_state.shots_in_burst = game_state.shots_in_burst + 1
game_state.burst_reset_timer = BURST_RESET_TIME
if wpn == SUIC_WEAPON_PISTOL do
game_state.recoil_pitch = game_state.recoil_pitch + PISTOL_KICK_PITCH
game_state.recoil_yaw = game_state.recoil_yaw + (get_random_value(-1000, 1000) as float / 1000.0) * PISTOL_KICK_YAW
game_state.cross_spread = game_state.cross_spread + PISTOL_CROSS_KICK + game_state.shots_in_burst as float * PISTOL_SPRAY_GROW
end
if wpn == SUIC_WEAPON_RIFLE do
game_state.recoil_pitch = game_state.recoil_pitch + RIFLE_KICK_PITCH
game_state.recoil_yaw = game_state.recoil_yaw + (get_random_value(-1000, 1000) as float / 1000.0) * RIFLE_KICK_YAW
game_state.cross_spread = game_state.cross_spread + RIFLE_CROSS_KICK + game_state.shots_in_burst as float * RIFLE_SPRAY_GROW
end
suic_net_send_shoot(my_id, game_state.client_tick)
end
end
fn suic_game_update(game_state: *GameState, dt: float) -> () do
suic_net_poll()
let my_id = suic_net_get_my_id()
if my_id != 255 and suic_net_player_present(my_id) do
game_state.prev_body_pos = game_state.current_target_body
game_state.current_target_body = suic_v3(suic_net_player_x(my_id),
suic_net_player_y(my_id),
suic_net_player_z(my_id))
game_state.interp_timer = 0.0
end
# Interpolate camera
if my_id != 255 and suic_net_player_present(my_id) do
let mut interp_factor = game_state.interp_timer / (1.0 / 20.0)
if interp_factor > 1.0 do
interp_factor = 1.0
end
let interp_body = suic_v3_add(suic_v3_mul(game_state.prev_body_pos, 1.0 - interp_factor),
suic_v3_mul(game_state.current_target_body, interp_factor))
game_state.cam_pos.x = interp_body.x
game_state.cam_pos.y = interp_body.y + 1.0
game_state.cam_pos.z = interp_body.z + 0.0001
let terrain_h = suic_terrain_height(game_state.cam_pos.x, game_state.cam_pos.z)
game_state.cam_pos.y = fmaxf(game_state.cam_pos.y, terrain_h + 1.5)
end
game_state.interp_timer = game_state.interp_timer + dt
# Forward offset
if my_id != 255 do
let view_yaw = game_state.yaw + game_state.recoil_yaw
let view_pitch = game_state.pitch + game_state.recoil_pitch
let forward = suic_v3(sinf(view_yaw) * cosf(view_pitch), sinf(view_pitch),
cosf(view_yaw) * cosf(view_pitch))
game_state.cam_pos = suic_v3_add(game_state.cam_pos, suic_v3_mul(forward, 0.3))
game_state.cam_pos.y = game_state.cam_pos.y - 0.2
end
let terrain_h = suic_terrain_height(game_state.cam_pos.x, game_state.cam_pos.z)
game_state.cam_pos.y = fmaxf(game_state.cam_pos.y, terrain_h + 1.5)
end
fn suic_game_render(game_state: *GameState) -> () do
let sw = get_screen_width()
let sh = get_screen_height()
let my_id = suic_net_get_my_id()
let view_yaw = game_state.yaw + game_state.recoil_yaw
let view_pitch = game_state.pitch + game_state.recoil_pitch
let mut clamped_view_pitch = view_pitch
if clamped_view_pitch < -1.5 do
clamped_view_pitch = -1.5
end
if clamped_view_pitch > 1.5 do
clamped_view_pitch = 1.5
end
let forward = suic_v3(sinf(view_yaw) * cosf(clamped_view_pitch), sinf(clamped_view_pitch),
cosf(view_yaw) * cosf(clamped_view_pitch))
# Camera setup (simplified - need to implement Camera3D struct)
let mut fov = 75.0
if game_state.scoped do
fov = 30.0
end
begin_mode3d(game_state.cam_pos.x, game_state.cam_pos.y, game_state.cam_pos.z,
game_state.cam_pos.x + forward.x, game_state.cam_pos.y + forward.y, game_state.cam_pos.z + forward.z,
0.0, 1.0, 0.0, fov, 0)
# Terrain
suic_terrain_draw(game_state.cam_pos)
# Border
let border_size = SUIC_TERRAIN_MAX - SUIC_TERRAIN_MIN
draw_cube(0.0, 0.0, 0.0, border_size, 1000.0, border_size, 255, 0, 0, 100)
# Items
let mut i = 0
while i < SUIC_NET_MAX_ITEMS do
if suic_net_item_present(i) do
let mut ic_r = 220
let mut ic_g = 220
let mut ic_b = 220
let item_type = suic_net_item_type(i)
if item_type == SUIC_ITEM_MEDKIT do
ic_r = 120
ic_g = 255
ic_b = 120
end
if item_type == SUIC_ITEM_AMMO_PISTOL do
ic_r = 255
ic_g = 220
ic_b = 120
end
if item_type == SUIC_ITEM_AMMO_RIFLE do
ic_r = 255
ic_g = 180
ic_b = 120
end
let item_pos = suic_v3(suic_net_item_x(i), suic_net_item_y(i), suic_net_item_z(i))
let item_normal = suic_v3(0.0, 1.0, 0.0)
let base_color = SuicColor { r: ic_r as u8, g: ic_g as u8, b: ic_b as u8, a: 255 }
let lit_color = suic_light_apply_shadows(base_color, item_normal, item_pos, &game_state.light)
draw_sphere(item_pos.x, item_pos.y, item_pos.z, 0.3, lit_color.r as int, lit_color.g as int, lit_color.b as int, lit_color.a as int)
end
i = i + 1
end
# Players
i = 0
while i < SUIC_NET_MAX_PLAYERS do
if suic_net_player_present(i) do
let mut interp_factor = game_state.interp_timer / (1.0 / 20.0)
if interp_factor > 1.0 do
interp_factor = 1.0
end
let prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i))
let curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i))
let p = suic_v3_add(suic_v3_mul(prev, 1.0 - interp_factor), suic_v3_mul(curr, interp_factor))
let mut c_r = 255
let mut c_g = 80
let mut c_b = 80
if i == my_id do
c_r = 80
c_g = 180
c_b = 255
end
if not suic_net_player_alive(i) do
c_r = 120
c_g = 120
c_b = 120
end
let player_normal = suic_v3(0.0, 1.0, 0.0)
let base_color = SuicColor { r: c_r as u8, g: c_g as u8, b: c_b as u8, a: 255 }
let lit_color = suic_light_apply_shadows(base_color, player_normal, p, &game_state.light)
draw_capsule(p.x, p.y - 0.5, p.z, p.x, p.y + 0.5, p.z, 0.35, 8, 8, lit_color.r as int, lit_color.g as int, lit_color.b as int, lit_color.a as int)
end
i = i + 1
end
end_mode3d()
# Nameplates
i = 0
while i < SUIC_NET_MAX_PLAYERS do
if suic_net_player_present(i) do
let mut interp_factor = game_state.interp_timer / (1.0 / 20.0)
if interp_factor > 1.0 do
interp_factor = 1.0
end
let prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i))
let curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i))
let p = suic_v3_add(suic_v3_mul(prev, 1.0 - interp_factor), suic_v3_mul(curr, interp_factor))
suic_ui_draw_nameplate(sw, sh, p.x, p.y, p.z, suic_net_player_username(i), i == my_id,
game_state.cam_pos.x, game_state.cam_pos.y, game_state.cam_pos.z,
game_state.cam_pos.x + forward.x, game_state.cam_pos.y + forward.y, game_state.cam_pos.z + forward.z)
end
i = i + 1
end
# HUD
if my_id == 255 or not suic_net_player_present(my_id) do
draw_text("Offline", 10, 10, 20, 255, 255, 255, 255)
end
if my_id != 255 and suic_net_player_present(my_id) do
let weapon = suic_net_player_weapon(my_id)
let mut mag = suic_net_player_rifle_mag(my_id)
let mut reserve = suic_net_player_rifle_ammo(my_id)
if weapon == SUIC_WEAPON_PISTOL do
mag = suic_net_player_pistol_mag(my_id)
reserve = suic_net_player_pistol_ammo(my_id)
end
suic_ui_draw_hud(suic_net_player_hp(my_id), weapon, mag, reserve, suic_net_player_medkits(my_id), suic_net_player_reload_time(my_id))
end
draw_fps(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, game_state.cross_spread, game_state.scoped)
end
fn main() -> int do
let zero_vec = SuicVec3 { x: 0.0, y: 0.0, z: 0.0 }
let default_light = SuicDirectionalLight { direction: zero_vec, color: zero_vec, intensity: 0.0, ambientIntensity: 0.0, shadowBias: 0.0, shadowIntensity: 0.0 }
# Game state
let mut game_state = GameState {
cam_pos: SuicVec3 { x: 0.0, y: 5.0, z: 6.0 },
yaw: 0.0,
pitch: 0.0,
prev_body_pos: SuicVec3 { x: 0.0, y: 5.0, z: 6.0 },
current_target_body: SuicVec3 { x: 0.0, y: 5.0, z: 6.0 },
interp_timer: 0.0,
recoil_yaw: 0.0,
recoil_pitch: 0.0,
cross_spread: 0.0,
scoped: false,
fire_cooldown: 0.0,
shots_in_burst: 0,
burst_reset_timer: 0.0,
client_tick: 0,
light: default_light
}
let screen_w = 1280
let screen_h = 720
init_window(screen_w, screen_h, "Soup")
defer close_window()
set_target_fps(120)
disable_cursor()
game_state.light = suic_light_create_default()
# Get username
let mut username = ""
suic_ui_username_prompt(username, SUIC_NET_USERNAME_MAX)
# Initialize game subsystems
suic_game_init(&game_state, username, "24.240.181.107", 6769) # 24.240.181.107 -> nishi.boats
# Main loop
while window_should_close() == false do
let dt = get_frame_time()
suic_game_handle_input(&game_state, dt)
suic_game_update(&game_state, dt)
begin_drawing()
clear_background(135, 206, 235, 255)
suic_game_render(&game_state)
end_drawing()
end
suic_game_shutdown()
0
end