455 lines
15 KiB
Text
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
|