From e0277a8807ac9297361cb9bcb5b8370b87cc8627 Mon Sep 17 00:00:00 2001 From: Masashi Date: Sat, 20 Dec 2025 03:10:01 +0530 Subject: [PATCH 1/4] HMMMMMMMMMMMMMMMMMMMM --- game/client_backup.sui | 549 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 549 insertions(+) create mode 100644 game/client_backup.sui diff --git a/game/client_backup.sui b/game/client_backup.sui new file mode 100644 index 0000000..096511e --- /dev/null +++ b/game/client_backup.sui @@ -0,0 +1,549 @@ +# Suicmez port of game/client.c + +# Constants +const PROTOCOL_VERSION = 67 +const SERVER_PORT = 27015 +const MAX_PLAYERS = 16 +const USERNAME_MAX = 16 +const TERRAIN_SIZE = 256 +const TERRAIN_SCALE = 1.0 +const TERRAIN_MIN = - (TERRAIN_SIZE * TERRAIN_SCALE / 2.0) +const TERRAIN_MAX = TERRAIN_SIZE * TERRAIN_SCALE / 2.0 + +const WEAPON_PISTOL = 0 +const WEAPON_RIFLE = 1 + +const ITEM_NONE = 0 +const ITEM_MEDKIT = 1 +const ITEM_AMMO_PISTOL = 2 +const ITEM_AMMO_RIFLE = 3 + +const MAX_ITEMS = 64 + +const BTN_RELOAD = (1 << 0) +const BTN_SWITCH_PISTOL = (1 << 1) +const BTN_SWITCH_RIFLE = (1 << 2) +const BTN_PICK = (1 << 3) +const BTN_USE_MEDKIT = (1 << 4) +const BTN_JUMP = (1 << 5) + +# Structs +struct DirectionalLight + direction: suic_vector3 + color: suic_vector3 + intensity: float + ambient_intensity: float + shadow_bias: float + shadow_intensity: float +end + +struct TerrainShader + shader: suic_shader_handle + loc_view_pos: int + loc_light_dir: int + loc_light_color: int + loc_light_intensity: int + loc_ambient_intensity: int + loc_terrain_color: int +end + +# Network message types +enum MsgType + MSG_HELLO + MSG_WELCOME + MSG_INPUT + MSG_SNAPSHOT + MSG_SHOOT + MSG_ITEMS + MSG_ROOM_STATE +end + +struct MsgHello + type_: u8 + protocol: u32 + username: string +end + +struct MsgWelcome + type_: u8 + player_id: u8 + server_tick: u32 +end + +struct MsgInput + type_: u8 + player_id: u8 + client_tick: u32 + move_x: float + move_z: float + yaw: float + pitch: float + buttons: u8 +end + +struct MsgShoot + type_: u8 + player_id: u8 + client_tick: u32 +end + +struct PlayerStateNet + id: u8 + alive: u8 + hp: i16 + x: float + y: float + z: float + yaw: float + pitch: float + weapon: u8 + pistol_mag: i16 + rifle_mag: i16 + pistol_ammo: i16 + rifle_ammo: i16 + medkits: i16 + reload_time_left: i16 + username: string +end + +struct MsgSnapshot + type_: u8 + server_tick: u32 + count: u8 + p: [PlayerStateNet] +end + +struct ItemNet + id: u16 + type_: u8 + qty: i16 + x: float + y: float + z: float +end + +struct MsgItems + type_: u8 + server_tick: u32 + count: u8 + items: [ItemNet] +end + +struct MsgRoomState + type_: u8 + state: u8 + countdown_remaining: float + winner_id: u8 + winner_name: string +end + +struct RemotePlayer + present: int + alive: int + hp: int + pos: suic_vector3 + prev_pos: suic_vector3 + yaw: float + pitch: float + weapon: u8 + pistol_mag: i16 + rifle_mag: i16 + pistol_ammo: i16 + rifle_ammo: i16 + medkits: i16 + reload_time_left: i16 + username: [u8; 16] +end + +struct WorldItem + present: int + id: u16 + type_: u8 + qty: i16 + pos: suic_vector3 +end + +# Main function +fn main() -> int do + let sw = 1280 + let sh = 720 + suic_init_window(sw, sh, "Voxel Shooter - Client (Suicmez)") + suic_set_target_fps(120) + + # Username prompt + let my_name = [0; 16] + suic_username_prompt(my_name) + + suic_disable_cursor() + + # Generate terrain mesh + let terrain_mesh = suic_generate_terrain_mesh() + let terrain_model = suic_load_model_from_mesh(terrain_mesh) + + # Setup directional light + let dir_light = DirectionalLight { + direction: suic_vector3 { x: -0.8, y: -1.0, z: -0.6 }, + color: suic_vector3 { x: 1.0, y: 1.0, z: 1.0 }, + intensity: 1.2, + ambient_intensity: 0.3, + shadow_bias: 0.005, + shadow_intensity: 0.4 + } + + let sock = suic_udp_socket_create() + if sock < 0 do + return 1 + end + suic_udp_set_nonblocking(sock) + + let my_id = 255 + let client_tick = 0 + + let rp = [RemotePlayer { present: 0 }; 16] + let wi = [WorldItem { present: 0 }; 64] + + # Room state + let room_state = 0 + let countdown_remaining = 0.0 + let winner_name = [0; 16] + + let cam_pos = suic_vector3 { x: 0, y: 5.0, z: 6 } + let yaw = 0.0 + let pitch = 0.0 + + let prev_body_pos = suic_vector3 { x: 0, y: 5.0, z: 6 } + let current_target_body = suic_vector3 { x: 0, y: 5.0, z: 6 } + let interp_timer = 0.0 + + let recoil_yaw = 0.0 + let recoil_pitch = 0.0 + let cross_spread = 0.0 + let scoped = 0 + let fire_cooldown = 0.0 + let shots_in_burst = 0 + let burst_reset_timer = 0.0 + + let pistol_fire_rate = 4.0 + let rifle_fire_rate = 12.0 + let recoil_return = 18.0 + let cross_return = 14.0 + let pistol_kick_pitch = 0.010 + let pistol_kick_yaw = 0.004 + let rifle_kick_pitch = 0.018 + let rifle_kick_yaw = 0.010 + let pistol_cross_kick = 2.0 + let rifle_cross_kick = 4.0 + let pistol_spray_grow = 0.4 + let rifle_spray_grow = 1.2 + let burst_reset_time = 0.18 + + let hello_buf = [0; 21] # sizeof(MsgHello) + suic_msg_hello_pack(hello_buf, MSG_HELLO, PROTOCOL_VERSION, my_name) + suic_udp_sendto(sock, hello_buf, 21, "127.0.0.1", SERVER_PORT) + + while not suic_window_should_close() do + client_tick = client_tick + 1 + let dt = suic_get_frame_time() + + fire_cooldown = fire_cooldown - dt + if fire_cooldown < 0.0 do fire_cooldown = 0.0 end + burst_reset_timer = burst_reset_timer - dt + if burst_reset_timer <= 0.0 do shots_in_burst = 0 end + + let k = 1.0 - suic_expf(-recoil_return * dt) + recoil_yaw = recoil_yaw + (0.0 - recoil_yaw) * k + recoil_pitch = recoil_pitch + (0.0 - recoil_pitch) * k + + k = 1.0 - suic_expf(-cross_return * dt) + cross_spread = cross_spread + (0.0 - cross_spread) * k + if cross_spread < 0.01 do cross_spread = 0.0 end + + # Network receive + let buf = [0; 1400] + let from_host = [0; 256] + let from_port = 0 + let n = suic_udp_recvfrom(sock, buf, 1400, from_host, from_port) + if n > 0 do + let type_ = buf[0] + if type_ == MSG_WELCOME do + my_id = suic_msg_welcome_unpack(buf) + end else if type_ == MSG_SNAPSHOT do + suic_msg_snapshot_unpack(buf, rp, 16) + if my_id != 255 and rp[my_id].present do + prev_body_pos = current_target_body + current_target_body = rp[my_id].pos + interp_timer = 0.0 + end + end else if type_ == MSG_ITEMS do + suic_msg_items_unpack(buf, wi, 64) + end else if type_ == MSG_ROOM_STATE do + suic_msg_room_state_unpack(buf, &room_state, &countdown_remaining, winner_name) + end + end + + # Interpolate camera position + if my_id != 255 and rp[my_id].present do + let interp_factor = interp_timer / (1.0 / 20.0) + if interp_factor > 1.0 do interp_factor = 1.0 end + let interp_body = suic_vector3 { + x: prev_body_pos.x * (1.0 - interp_factor) + current_target_body.x * interp_factor, + y: prev_body_pos.y * (1.0 - interp_factor) + current_target_body.y * interp_factor, + z: prev_body_pos.z * (1.0 - interp_factor) + current_target_body.z * interp_factor + } + cam_pos.x = interp_body.x + cam_pos.y = interp_body.y + 1.0 + cam_pos.z = interp_body.z + 0.0001 + + # Prevent camera from clipping into terrain + let terrain_h = suic_get_terrain_height(cam_pos.x, cam_pos.z) + cam_pos.y = suic_fmaxf(cam_pos.y, terrain_h + 1.5) + end + interp_timer = interp_timer + dt + + let md = suic_get_mouse_delta() + let sens = 0.0025 + yaw = yaw - md.x * sens + pitch = pitch - md.y * sens + if pitch < -1.5 do pitch = -1.5 end + if pitch > 1.5 do pitch = 1.5 end + + let view_yaw = yaw + recoil_yaw + let view_pitch = pitch + recoil_pitch + if view_pitch < -1.5 do view_pitch = -1.5 end + if view_pitch > 1.5 do view_pitch = 1.5 end + + let move_x = 0.0 + let move_z = 0.0 + if suic_is_key_down(KEY_W) do move_z = move_z + 1.0 end + if suic_is_key_down(KEY_A) do move_x = move_x + 1.0 end + if suic_is_key_down(KEY_S) do move_z = move_z - 1.0 end + if suic_is_key_down(KEY_D) do move_x = move_x - 1.0 end + + let buttons = 0 + if suic_is_key_pressed(KEY_ONE) do buttons = buttons bitor BTN_SWITCH_PISTOL end + if suic_is_key_pressed(KEY_TWO) do buttons = buttons bitor BTN_SWITCH_RIFLE end + if suic_is_key_pressed(KEY_R) do buttons = buttons bitor BTN_RELOAD end + if suic_is_key_pressed(KEY_F) do buttons = buttons bitor BTN_PICK end + if suic_is_key_pressed(KEY_H) do buttons = buttons bitor BTN_USE_MEDKIT end + if suic_is_key_pressed(KEY_SPACE) do buttons = buttons bitor BTN_JUMP end + if suic_is_key_pressed(KEY_Z) do scoped = not scoped end + + if my_id != 255 do + let in_buf = [0; 23] # sizeof(MsgInput) + suic_msg_input_pack(in_buf, MSG_INPUT, my_id, client_tick, move_x, move_z, view_yaw, view_pitch, buttons as u8) + suic_udp_sendto(sock, in_buf, 23, "127.0.0.1", SERVER_PORT) + end + + if my_id != 255 and suic_is_mouse_button_down(MOUSE_BUTTON_LEFT) and fire_cooldown <= 0.0 and rp[my_id].present do + let wpn = rp[my_id].weapon + let rate = rifle_fire_rate + if wpn == WEAPON_PISTOL do rate = pistol_fire_rate end + fire_cooldown = 1.0 / rate + + shots_in_burst = shots_in_burst + 1 + burst_reset_timer = burst_reset_time + + if wpn == WEAPON_PISTOL do + recoil_pitch = recoil_pitch + pistol_kick_pitch + recoil_yaw = recoil_yaw + (suic_get_random_value(-1000, 1000) as float / 1000.0) * pistol_kick_yaw + cross_spread = cross_spread + pistol_cross_kick + shots_in_burst as float * pistol_spray_grow + end else do + recoil_pitch = recoil_pitch + rifle_kick_pitch + recoil_yaw = recoil_yaw + (suic_get_random_value(-1000, 1000) as float / 1000.0) * rifle_kick_yaw + cross_spread = cross_spread + rifle_cross_kick + shots_in_burst as float * rifle_spray_grow + end + + let sh_buf = [0; 6] # sizeof(MsgShoot) + suic_msg_shoot_pack(sh_buf, MSG_SHOOT, my_id, client_tick) + suic_udp_sendto(sock, sh_buf, 6, "127.0.0.1", SERVER_PORT) + end + + let forward = suic_vector3 { + x: suic_sinf(view_yaw) * suic_cosf(view_pitch), + y: suic_sinf(view_pitch), + z: suic_cosf(view_yaw) * suic_cosf(view_pitch) + } + + # Offset camera position: 0.3m in front, 0.2m below + cam_pos = suic_vector3 { + x: cam_pos.x + forward.x * 0.3, + y: cam_pos.y + forward.y * 0.3 - 0.2, + z: cam_pos.z + forward.z * 0.3 + } + + # Ensure camera doesn't clip into terrain after offset + let terrain_h = suic_get_terrain_height(cam_pos.x, cam_pos.z) + cam_pos.y = suic_fmaxf(cam_pos.y, terrain_h + 1.5) + + suic_begin_drawing() + suic_clear_background(135, 206, 235, 255) # Sky blue + + let fovy = 75.0 + if scoped do fovy = 30.0 end + suic_begin_mode3d(cam_pos.x, cam_pos.y, cam_pos.z, forward.x, forward.y, forward.z, suic_vector3 { x: 0, y: 1, z: 0 }, fovy, 0) + + # Draw terrain + suic_draw_model(terrain_model, 0, 0, 0, 1.0, 1.0, 1.0, 80, 140, 70, 255) + + # Draw border + suic_draw_cube(0, 0, 0, 256.0, 1000.0, 256.0, 255, 0, 0, 100) + + # Items + let i = 0 + while i < 64 do + if wi[i].present do + let ic = suic_color { r: 220, g: 220, b: 220, a: 255 } + if wi[i].type_ == ITEM_MEDKIT do + ic = suic_color { r: 120, g: 255, b: 120, a: 255 } + end else do + if wi[i].type_ == ITEM_AMMO_PISTOL do + ic = suic_color { r: 255, g: 220, b: 120, a: 255 } + end else do + if wi[i].type_ == ITEM_AMMO_RIFLE do + ic = suic_color { r: 255, g: 180, b: 120, a: 255 } + end + end + end + + # Apply basic lighting + let item_normal = suic_vector3 { x: 0, y: 1, z: 0 } + let lit_color = suic_apply_lighting_with_shadows(ic, item_normal, wi[i].pos, dir_light.direction, dir_light.intensity, dir_light.ambient_intensity, dir_light.shadow_bias) + suic_draw_sphere(wi[i].pos.x, wi[i].pos.y, wi[i].pos.z, 0.3, lit_color.r, lit_color.g, lit_color.b, lit_color.a) + end + i = i + 1 + end + + # Players + let i = 0 + while i < 16 do + if rp[i].present do + let interp_factor = interp_timer / (1.0 / 20.0) + if interp_factor > 1.0 do interp_factor = 1.0 end + let p = suic_vector3 { + x: rp[i].prev_pos.x * (1.0 - interp_factor) + rp[i].pos.x * interp_factor, + y: rp[i].prev_pos.y * (1.0 - interp_factor) + rp[i].pos.y * interp_factor, + z: rp[i].prev_pos.z * (1.0 - interp_factor) + rp[i].pos.z * interp_factor + } + let c = suic_color { r: 255, g: 80, b: 80, a: 255 } + if i == my_id do + c = suic_color { r: 80, g: 180, b: 255, a: 255 } + end + if not rp[i].alive do + c = suic_color { r: 120, g: 120, b: 120, a: 255 } + end + + # Apply basic lighting + let player_normal = suic_vector3 { x: 0, y: 1, z: 0 } + let lit_player_color = suic_apply_lighting_with_shadows(c, player_normal, p, dir_light.direction, dir_light.intensity, dir_light.ambient_intensity, dir_light.shadow_bias) + suic_draw_capsule(p.x, p.y - 0.5, p.z, p.x, p.y + 0.5, p.z, 0.35, 8, 8, lit_player_color.r, lit_player_color.g, lit_player_color.b, lit_player_color.a) + end + i = i + 1 + end + + suic_end_mode3d() + + # Nameplates - removed for debugging + + # HUD + if my_id == 255 or not rp[my_id].present do + suic_draw_text("Connecting...", 10, 10, 20, 255, 255, 255, 255) + end + if my_id != 255 and rp[my_id].present do + suic_draw_rectangle(10, 10, 280, 110, 0, 0, 0, 120) + suic_draw_text("HP", 20, 20, 20, 255, 255, 255, 255) + let health_bar_width = (rp[my_id].hp * 220) / 100 + let health_color = suic_color { r: 255, g: 80, b: 80, a: 120 } + if rp[my_id].hp > 60 do + health_color = suic_color { r: 80, g: 255, b: 80, a: 120 } + end else do + if rp[my_id].hp > 30 do + health_color = suic_color { r: 255, g: 200, b: 80, a: 120 } + end + end + suic_draw_rectangle(20, 45, 220, 20, 40, 40, 40, 255) + suic_draw_rectangle(20, 45, health_bar_width, 20, health_color.r, health_color.g, health_color.b, health_color.a) + suic_draw_text(suic_text_format("%d", rp[my_id].hp), 250, 47, 18, 255, 255, 255, 255) + let medkit_color_r = 120 + let medkit_color_g = 120 + let medkit_color_b = 120 + if rp[my_id].medkits > 0 do + medkit_color_g = 255 + medkit_color_b = 120 + end + suic_draw_text(suic_text_format("Medkits: %d (H to use)", rp[my_id].medkits), 20, 75, 18, 120, medkit_color_g, medkit_color_b, 120) + suic_draw_text("1=Pistol 2=Rifle R=Reload F=Pick SPACE=Jump", 20, 95, 12, 150, 150, 150, 150) + + let weapon_name = "RIFLE" + let weapon_color = suic_color { r: 255, g: 150, b: 100, a: 255 } + let current_mag = rp[my_id].rifle_mag + let reserve_ammo = rp[my_id].rifle_ammo + if rp[my_id].weapon == WEAPON_PISTOL do + weapon_name = "PISTOL" + weapon_color = suic_color { r: 100, g: 200, b: 255, a: 255 } + current_mag = rp[my_id].pistol_mag + reserve_ammo = rp[my_id].pistol_ammo + end + + suic_draw_rectangle(sw - 260, sh - 120, 250, 110, 0, 0, 0, 180) + suic_draw_text(weapon_name, sw - 250, sh - 110, 28, weapon_color.r, weapon_color.g, weapon_color.b, weapon_color.a) + suic_draw_text(suic_text_format("%d", current_mag), sw - 250, sh - 75, 40, 255, 255, 255, 255) + suic_draw_text(suic_text_format("/ %d", reserve_ammo), sw - 140, sh - 65, 24, 180, 180, 180, 255) + if rp[my_id].reload_time_left > 0 do + suic_draw_text("RELOADING...", sw - 250, sh - 30, 20, 255, 200, 80, 255) + end else if current_mag == 0 do + suic_draw_text("RELOAD!", sw - 250, sh - 30, 20, 255, 80, 80, 255) + end + end + + suic_draw_fps(sw - 90, 10) + + # Room state overlay + if room_state == 0 do # Waiting + suic_draw_rectangle(sw / 2 - 150, sh / 2 - 50, 350, 100, 0, 0, 0, 200) + suic_draw_text("WAITING FOR PLAYERS", sw / 2 - 120, sh / 2 - 30, 24, 255, 255, 255, 255) + suic_draw_text("Need at least 2 players", sw / 2 - 100, sh / 2 - 5, 18, 200, 200, 200, 255) + end + if room_state == 1 do # Counting down + suic_draw_rectangle(sw / 2 - 150, sh / 2 - 50, 350, 100, 0, 0, 0, 200) + suic_draw_text("GAME STARTING SOON", sw / 2 - 120, sh / 2 - 30, 24, 255, 255, 80, 255) + suic_draw_text(suic_text_format("%.1f seconds", countdown_remaining), sw / 2 - 60, sh / 2 - 5, 20, 255, 255, 255, 255) + end + if room_state == 3 do # Finished + suic_draw_rectangle(sw / 2 - 200, sh / 2 - 50, 450, 100, 0, 0, 0, 200) + suic_draw_text("GAME FINISHED", sw / 2 - 80, sh / 2 - 30, 28, 255, 80, 80, 255) + if winner_name[0] do + suic_draw_text(suic_text_format("Winner: %s", winner_name), sw / 2 - 100, sh / 2 - 5, 24, 255, 255, 80, 255) + end + if not winner_name[0] do + suic_draw_text("No winner", sw / 2 - 50, sh / 2 - 5, 24, 255, 255, 255, 255) + end + end + + # Crosshair + let cx = sw / 2 + let cy = sh / 2 + let gap = 6 + cross_spread as int + let len = 10 + let radius = 3.0 + if scoped do + gap = 2 + len = 5 + radius = 1.5 + end + let thick = 2 + let col = suic_color { r: 240, g: 240, b: 245, a: 220 } + suic_draw_line(cx - gap - len, cy - thick / 2, cx - gap, cy - thick / 2, col.r, col.g, col.b, col.a) + suic_draw_line(cx + gap, cy - thick / 2, cx + gap + len, cy - thick / 2, col.r, col.g, col.b, col.a) + suic_draw_line(cx - thick / 2, cy - gap - len, cx - thick / 2, cy - gap, col.r, col.g, col.b, col.a) + suic_draw_line(cx - thick / 2, cy + gap, cx - thick / 2, cy + gap + len, col.r, col.g, col.b, col.a) + suic_draw_circle(cx, cy, radius, 240, 240, 245, 160) + + suic_end_drawing() + end + + suic_unload_model(terrain_model) + suic_unload_mesh(terrain_mesh) + suic_close_window() + suic_udp_socket_close(sock) + 0 +end From a222b3807ea2537238c40e0cf963449dbf542312 Mon Sep 17 00:00:00 2001 From: Masashi Date: Sat, 20 Dec 2025 03:16:16 +0530 Subject: [PATCH 2/4] unsafe --- src/import_resolver.rs | 29 +++- src/main.rs | 315 +++++++++++++++++++++++++++-------------- tests/arrays.c | 21 +++ tests/basic_types.c | 21 +++ tests/control_flow.c | 29 +++- tests/functions.c | 23 ++- tests/structs.c | 25 +++- 7 files changed, 354 insertions(+), 109 deletions(-) diff --git a/src/import_resolver.rs b/src/import_resolver.rs index 4c3a360..067e276 100644 --- a/src/import_resolver.rs +++ b/src/import_resolver.rs @@ -81,6 +81,8 @@ struct ParsedFile { nodes: Vec, /// Hash of the file content at parse time content_hash: u64, + /// Whether this file is in unsafe mode + is_unsafe: bool, } /// Tracks global symbols and their definitions @@ -124,6 +126,8 @@ pub struct ImportResolver { processing_stack: HashSet, /// Dependency graph: file -> list of files it depends on dependency_graph: HashMap>, + /// Whether any file in the current compilation is unsafe + has_unsafe_files: bool, } impl ImportResolver { @@ -133,6 +137,7 @@ impl ImportResolver { symbol_registry: GlobalSymbolRegistry::default(), processing_stack: HashSet::new(), dependency_graph: HashMap::new(), + has_unsafe_files: false, } } @@ -191,6 +196,7 @@ impl ImportResolver { })?; let content_hash = Self::hash_content(&source); + let is_unsafe = source.trim_start().starts_with("# UNSAFE"); // Check if we have a valid cached version if let Some(cached) = self.parse_cache.get(filename) { @@ -211,6 +217,7 @@ impl ImportResolver { ParsedFile { nodes: nodes.clone(), content_hash, + is_unsafe, }, ); @@ -295,6 +302,13 @@ impl ImportResolver { let nodes = self.parse_file(filename)?; let mut result = Vec::new(); + // Check if this file is unsafe + if let Some(cached) = self.parse_cache.get(filename) { + if cached.is_unsafe { + self.has_unsafe_files = true; + } + } + // Collect dependencies let deps = self.collect_dependencies(filename, &nodes); self.dependency_graph @@ -393,10 +407,23 @@ impl ImportResolver { Ok(result) } + /// Check if the current compilation contains any unsafe files + pub fn has_unsafe_files(&self) -> bool { + self.has_unsafe_files + } + /// Resolve all imports starting from the given file pub fn resolve(&mut self, filename: &str) -> Result, ImportError> { println!("Starting import resolution..."); - self.resolve_imports_recursive(filename) + self.has_unsafe_files = false; // Reset for new compilation + let result = self.resolve_imports_recursive(filename); + // Check if the main file is unsafe + if let Ok(source) = fs::read_to_string(filename) { + if source.trim_start().starts_with("# UNSAFE") { + self.has_unsafe_files = true; + } + } + result } } diff --git a/src/main.rs b/src/main.rs index 83cd7c1..85f7886 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,103 @@ use clap::Parser; use std::fs; use suicmez::{ + ast::*, codegen::transpiler::Transpiler, import_resolver::ImportResolver, lambda_lower::LambdaLowerer, monomorphize::{Monomorphizer, check_no_typevars}, - typechecker::TypeChecker, + typechecker::{Type, TypeChecker}, }; +/// Convert ASTNode to TypedASTNode for unsafe mode (with dummy types) +fn convert_to_typed_ast(nodes: &[ASTNode]) -> Vec { + nodes.iter().map(|node| { + let dummy_type = Type::Unit; // Use unit type as dummy + let dummy_expr = TypedExpr { + kind: TypedExprKind::Int(0), // dummy expression + span: Span::new(&(0..0), "dummy".to_string()), + attributes: vec![], + ty: dummy_type.clone(), + }; + + let kind = match &node.kind { + ASTNodeKind::Function(f) => TypedASTNodeKind::Function(TypedFunction { + name: f.name.clone(), + parameters: f.parameters.clone(), + args: f.args.iter().map(|(name, typ)| { + (BindingId(0), name.clone(), typ.clone()) // dummy binding id + }).collect(), + return_type: f.return_type.clone(), + body: dummy_expr.clone(), + ty: dummy_type.clone(), + }), + ASTNodeKind::Const(c) => TypedASTNodeKind::Const(TypedConst { + name: c.name.clone(), + typ: c.typ.clone(), + value: dummy_expr.clone(), + }), + ASTNodeKind::Struct(s) => TypedASTNodeKind::Struct(TypedStruct { + name: s.name.clone(), + parameters: s.parameters.clone(), + fields: s.fields.iter().map(|f| TypedField { + name: f.name.clone(), + field_type: f.field_type.clone(), + span: f.span.clone(), + }).collect(), + }), + ASTNodeKind::Enum(e) => TypedASTNodeKind::Enum(TypedEnum { + name: e.name.clone(), + parameters: e.parameters.clone(), + variants: e.variants.iter().map(|v| TypedVariant { + name: v.name.clone(), + fields: v.fields.clone(), + span: v.span.clone(), + }).collect(), + }), + ASTNodeKind::Impl(i) => TypedASTNodeKind::Impl(TypedImpl { + target: i.target.clone(), + trait_name: i.trait_name.clone(), + methods: i.methods.iter().map(|m| TypedFunction { + name: m.name.clone(), + parameters: m.parameters.clone(), + args: m.args.iter().map(|(name, typ)| { + (BindingId(0), name.clone(), typ.clone()) + }).collect(), + return_type: m.return_type.clone(), + body: dummy_expr.clone(), + ty: dummy_type.clone(), + }).collect(), + }), + ASTNodeKind::Trait(t) => TypedASTNodeKind::Trait(TypedTrait { + name: t.name.clone(), + methods: t.methods.clone(), + parameters: t.parameters.clone(), + associated_types: vec![], + }), + ASTNodeKind::Extern(e) => TypedASTNodeKind::Extern(TypedExtern { + name: e.name.clone(), + args: e.args.clone(), + return_type: e.return_type.clone(), + from: e.from.clone(), + span: e.span.clone(), + }), + ASTNodeKind::Load(l) => TypedASTNodeKind::Load(TypedLoad { + library: l.library.clone(), + alias: l.alias.clone(), + span: l.span.clone(), + }), + ASTNodeKind::Use(u) => TypedASTNodeKind::Use(u.clone()), + }; + + TypedASTNode { + kind, + span: node.span.clone(), + attributes: node.attributes.clone(), + ty: dummy_type, + } + }).collect() +} + #[derive(Parser)] #[command(author, version, about = "A compiler for the Sui language")] struct Args { @@ -206,6 +296,8 @@ fn run_file(filename: &str, debug: bool) -> Result<(), String> { } })?; + let is_unsafe = resolver.has_unsafe_files(); + println!( "Import resolution complete! {} total nodes loaded", ast_nodes.len() @@ -226,59 +318,126 @@ fn run_file(filename: &str, debug: bool) -> Result<(), String> { lowered_nodes.len() ); - // Typecheck the AST - let mut typechecker = TypeChecker::new(); - let typed_nodes = typechecker - .typecheck_program(&lowered_nodes) - .map_err(|e| format_type_error(&source, &e))?; + let typed_nodes = if is_unsafe { + println!("Unsafe mode detected - skipping type checking"); + // Convert AST nodes to typed nodes with dummy types + convert_to_typed_ast(&lowered_nodes) + } else { + // Typecheck the AST + let mut typechecker = TypeChecker::new(); + typechecker + .typecheck_program(&lowered_nodes) + .map_err(|e| format_type_error(&source, &e))? + }; - println!( - "Type checking passed! {} nodes typechecked.", - typed_nodes.len() - ); + if is_unsafe { + println!("Unsafe mode - skipping monomorphization and type variable checks"); + } else { + println!( + "Type checking passed! {} nodes typechecked.", + typed_nodes.len() + ); - // Debug: show typed nodes - println!("\nTyped AST nodes before monomorphization:"); - for (i, node) in typed_nodes.iter().enumerate() { - let node_type = match &node.kind { - suicmez::ast::TypedASTNodeKind::Function(f) => { - format!("Function({})", f.name) - } - suicmez::ast::TypedASTNodeKind::Const(c) => { - format!("Const({})", c.name) - } - suicmez::ast::TypedASTNodeKind::Struct(s) => { - format!("Struct({}) with {} params", s.name, s.parameters.len()) - } - suicmez::ast::TypedASTNodeKind::Enum(e) => { - format!("Enum({}) with {} params", e.name, e.parameters.len()) - } - suicmez::ast::TypedASTNodeKind::Impl(imp) => { - format!("Impl({})", imp.target) - } - suicmez::ast::TypedASTNodeKind::Trait(t) => { - format!("Trait({})", t.name) - } - suicmez::ast::TypedASTNodeKind::Extern(e) => { - format!("Extern({})", e.name) - } - suicmez::ast::TypedASTNodeKind::Load(l) => { - format!("Load({})", l.alias) - } - suicmez::ast::TypedASTNodeKind::Use(u) => { - format!("Use({})", u.path) - } - }; - println!(" [{}] {}", i, node_type); + // Debug: show typed nodes + println!("\nTyped AST nodes before monomorphization:"); + for (i, node) in typed_nodes.iter().enumerate() { + let node_type = match &node.kind { + suicmez::ast::TypedASTNodeKind::Function(f) => { + format!("Function({})", f.name) + } + suicmez::ast::TypedASTNodeKind::Const(c) => { + format!("Const({})", c.name) + } + suicmez::ast::TypedASTNodeKind::Struct(s) => { + format!("Struct({}) with {} params", s.name, s.parameters.len()) + } + suicmez::ast::TypedASTNodeKind::Enum(e) => { + format!("Enum({}) with {} params", e.name, e.parameters.len()) + } + suicmez::ast::TypedASTNodeKind::Impl(imp) => { + format!("Impl({})", imp.target) + } + suicmez::ast::TypedASTNodeKind::Trait(t) => { + format!("Trait({})", t.name) + } + suicmez::ast::TypedASTNodeKind::Extern(e) => { + format!("Extern({})", e.name) + } + suicmez::ast::TypedASTNodeKind::Load(l) => { + format!("Load({})", l.alias) + } + suicmez::ast::TypedASTNodeKind::Use(u) => { + format!("Use({})", u.path) + } + }; + println!(" [{}] {}", i, node_type); + } } - // Monomorphize the AST - let monomorphizer = Monomorphizer::new(); - let mono_nodes = monomorphizer - .monomorphize_program(&typed_nodes) - .map_err(|e| { + let final_nodes = if is_unsafe { + // Skip monomorphization for unsafe mode + typed_nodes + } else { + // Monomorphize the AST + let monomorphizer = Monomorphizer::new(); + let mono_nodes = monomorphizer + .monomorphize_program(&typed_nodes) + .map_err(|e| { + format!( + "Monomorphization error: {}{}", + e.message, + if let Some(span) = &e.span { + format!(" at {}:{}", span.file, span.start) + } else { + String::new() + } + ) + })?; + + println!( + "Monomorphization passed! {} nodes after specialization.", + mono_nodes.len() + ); + + // Print detailed info about each node + println!("\nMonomorphized AST nodes:"); + for (i, node) in mono_nodes.iter().enumerate() { + let node_type = match &node.kind { + suicmez::ast::TypedASTNodeKind::Function(f) => { + format!("Function({})", f.name) + } + suicmez::ast::TypedASTNodeKind::Const(c) => { + format!("Const({})", c.name) + } + suicmez::ast::TypedASTNodeKind::Struct(s) => { + format!("Struct({}) with {} params", s.name, s.parameters.len()) + } + suicmez::ast::TypedASTNodeKind::Enum(e) => { + format!("Enum({}) with {} params", e.name, e.parameters.len()) + } + suicmez::ast::TypedASTNodeKind::Impl(imp) => { + format!("Impl({})", imp.target) + } + suicmez::ast::TypedASTNodeKind::Trait(t) => { + format!("Trait({})", t.name) + } + suicmez::ast::TypedASTNodeKind::Extern(e) => { + format!("Extern({})", e.name) + } + suicmez::ast::TypedASTNodeKind::Load(l) => { + format!("Load({})", l.alias) + } + suicmez::ast::TypedASTNodeKind::Use(u) => { + format!("Use({})", u.path) + } + }; + println!(" [{}] {}", i, node_type); + } + + // Check that no type variables remain + check_no_typevars(&mono_nodes).map_err(|e| { format!( - "Monomorphization error: {}{}", + "Type variable check failed: {}{}", e.message, if let Some(span) = &e.span { format!(" at {}:{}", span.file, span.start) @@ -288,65 +447,15 @@ fn run_file(filename: &str, debug: bool) -> Result<(), String> { ) })?; - println!( - "Monomorphization passed! {} nodes after specialization.", - mono_nodes.len() - ); + println!("Type variable check passed! No type variables remain in AST."); - // Print detailed info about each node - println!("\nMonomorphized AST nodes:"); - for (i, node) in mono_nodes.iter().enumerate() { - let node_type = match &node.kind { - suicmez::ast::TypedASTNodeKind::Function(f) => { - format!("Function({})", f.name) - } - suicmez::ast::TypedASTNodeKind::Const(c) => { - format!("Const({})", c.name) - } - suicmez::ast::TypedASTNodeKind::Struct(s) => { - format!("Struct({}) with {} params", s.name, s.parameters.len()) - } - suicmez::ast::TypedASTNodeKind::Enum(e) => { - format!("Enum({}) with {} params", e.name, e.parameters.len()) - } - suicmez::ast::TypedASTNodeKind::Impl(imp) => { - format!("Impl({})", imp.target) - } - suicmez::ast::TypedASTNodeKind::Trait(t) => { - format!("Trait({})", t.name) - } - suicmez::ast::TypedASTNodeKind::Extern(e) => { - format!("Extern({})", e.name) - } - suicmez::ast::TypedASTNodeKind::Load(l) => { - format!("Load({})", l.alias) - } - suicmez::ast::TypedASTNodeKind::Use(u) => { - format!("Use({})", u.path) - } - }; - println!(" [{}] {}", i, node_type); - } - - // Check that no type variables remain - check_no_typevars(&mono_nodes).map_err(|e| { - format!( - "Type variable check failed: {}{}", - e.message, - if let Some(span) = &e.span { - format!(" at {}:{}", span.file, span.start) - } else { - String::new() - } - ) - })?; - - println!("Type variable check passed! No type variables remain in AST."); + mono_nodes + }; // Generate C code let mut transpiler = Transpiler::new(debug); let c_code = transpiler - .transpile_program(&mono_nodes) + .transpile_program(&final_nodes) .map_err(|e| format!("Code generation error: {}", e))?; // Write C code to file diff --git a/tests/arrays.c b/tests/arrays.c index e9aed4f..7e33cc5 100644 --- a/tests/arrays.c +++ b/tests/arrays.c @@ -23,7 +23,28 @@ static void* suic_alloc_struct(const TypeInfo* type, size_t size, void* init_dat } +struct Color { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +}; +struct Shader { + int id; +}; +static const uint8_t sui_bitmap_Color[] = { 0, 0, 0, 0 }; +static const TypeInfo sui_typeinfo_Color = { + .field_count = 4, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_Color +}; +static const uint8_t sui_bitmap_Shader[] = { 0 }; +static const TypeInfo sui_typeinfo_Shader = { + .field_count = 1, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_Shader +}; int suic_main(void); diff --git a/tests/basic_types.c b/tests/basic_types.c index 27443d9..eb6eb33 100644 --- a/tests/basic_types.c +++ b/tests/basic_types.c @@ -23,7 +23,28 @@ static void* suic_alloc_struct(const TypeInfo* type, size_t size, void* init_dat } +struct Color { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +}; +struct Shader { + int id; +}; +static const uint8_t sui_bitmap_Color[] = { 0, 0, 0, 0 }; +static const TypeInfo sui_typeinfo_Color = { + .field_count = 4, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_Color +}; +static const uint8_t sui_bitmap_Shader[] = { 0 }; +static const TypeInfo sui_typeinfo_Shader = { + .field_count = 1, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_Shader +}; int suic_main(void); diff --git a/tests/control_flow.c b/tests/control_flow.c index edba436..69edccc 100644 --- a/tests/control_flow.c +++ b/tests/control_flow.c @@ -1,4 +1,4 @@ -#include "../libsuicmez/libsuicmez.h" +#include "libsuicmez/libsuicmez.h" #include #include #include @@ -23,13 +23,38 @@ static void* suic_alloc_struct(const TypeInfo* type, size_t size, void* init_dat } +struct Color { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +}; +struct Shader { + int id; +}; +static const uint8_t sui_bitmap_Color[] = { 0, 0, 0, 0 }; +static const TypeInfo sui_typeinfo_Color = { + .field_count = 4, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_Color +}; +static const uint8_t sui_bitmap_Shader[] = { 0 }; +static const TypeInfo sui_typeinfo_Shader = { + .field_count = 1, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_Shader +}; int suic_main(void); int suic_main(void) { - (true ? 1 : 0); + if (true) { + 1; + } else { + 0; + } int i = 0; while ((i < 5)) { i = (i + 1); diff --git a/tests/functions.c b/tests/functions.c index 08378bb..ec00f8d 100644 --- a/tests/functions.c +++ b/tests/functions.c @@ -1,4 +1,4 @@ -#include "../libsuicmez/libsuicmez.h" +#include "libsuicmez/libsuicmez.h" #include #include #include @@ -23,7 +23,28 @@ static void* suic_alloc_struct(const TypeInfo* type, size_t size, void* init_dat } +struct Color { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +}; +struct Shader { + int id; +}; +static const uint8_t sui_bitmap_Color[] = { 0, 0, 0, 0 }; +static const TypeInfo sui_typeinfo_Color = { + .field_count = 4, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_Color +}; +static const uint8_t sui_bitmap_Shader[] = { 0 }; +static const TypeInfo sui_typeinfo_Shader = { + .field_count = 1, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_Shader +}; int add(int x, int y); int suic_main(void); diff --git a/tests/structs.c b/tests/structs.c index 6faa376..16bda91 100644 --- a/tests/structs.c +++ b/tests/structs.c @@ -31,6 +31,15 @@ struct Person { char* name; int age; }; +struct Color { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +}; +struct Shader { + int id; +}; static const uint8_t sui_bitmap_Point[] = { 0, 0 }; static const TypeInfo sui_typeinfo_Point = { @@ -44,13 +53,25 @@ static const TypeInfo sui_typeinfo_Person = { .pointer_count = 1, .pointer_bitmap = sui_bitmap_Person }; +static const uint8_t sui_bitmap_Color[] = { 0, 0, 0, 0 }; +static const TypeInfo sui_typeinfo_Color = { + .field_count = 4, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_Color +}; +static const uint8_t sui_bitmap_Shader[] = { 0 }; +static const TypeInfo sui_typeinfo_Shader = { + .field_count = 1, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_Shader +}; int suic_main(void); int suic_main(void) { - struct Point* p = suic_alloc_struct(&sui_typeinfo_Point, sizeof(struct Point), &(struct Point){ .x = 5, .y = 10 }); - struct Person* person = suic_alloc_struct(&sui_typeinfo_Person, sizeof(struct Person), &(struct Person){ .name = suic_alloc_array(NULL, sizeof(char), 6, "Alice"), .age = 30 }); + struct Point* p = (struct Point){ .x = 5, .y = 10 }; + struct Person* person = (struct Person){ .name = suic_alloc_array(NULL, sizeof(char), 6, "Alice"), .age = 30 }; int _ = ((*p).x + (*person).age); return 0; } From a00e8690fd6eacc64285f0611cc983ba5de9d4cf Mon Sep 17 00:00:00 2001 From: Masashi Date: Sat, 20 Dec 2025 03:18:31 +0530 Subject: [PATCH 3/4] unsafe --- src/codegen/transpiler.rs | 10 +++++++++- tests/structs.c | 29 ++++++----------------------- tests/structs.o | Bin 45488 -> 75936 bytes 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/codegen/transpiler.rs b/src/codegen/transpiler.rs index 79772fe..ec2a448 100644 --- a/src/codegen/transpiler.rs +++ b/src/codegen/transpiler.rs @@ -337,6 +337,10 @@ impl Transpiler { } fn generate_struct_decl(&self, struct_decl: &CStructDecl) -> String { + // Skip predefined structs that are already declared in included headers + if matches!(struct_decl.name.as_str(), "Shader" | "Color") { + return String::new(); + } let mut output = format!("struct {} {{\n", struct_decl.name); for field in &struct_decl.fields { output.push_str(&format!(" {} {};\n", field.ty.to_string(), field.name)); @@ -346,6 +350,10 @@ impl Transpiler { } fn generate_typeinfo_decl(&self, struct_decl: &CStructDecl) -> String { + // Skip predefined structs + if matches!(struct_decl.name.as_str(), "Shader" | "Color") { + return String::new(); + } if let Some(bitmap) = self.typeinfo_map.get(&struct_decl.name) { // Generate pointer bitmap as a C array let bitmap_str = bitmap @@ -796,7 +804,7 @@ impl Transpiler { "Vec3" => "suic_vec3".to_string(), _ => format!("struct {}", struct_name), }; - format!("({}){{ {} }}", c_type_name, field_inits.join(", ")) + format!("suic_alloc_struct(&sui_typeinfo_{}, sizeof({}), &(({}){{ {} }}))", struct_name, c_type_name, c_type_name, field_inits.join(", ")) } CExpr::EnumLit(enum_name, variant_name, args) => { // Find the variant index - for simplicity, assume variants are in order diff --git a/tests/structs.c b/tests/structs.c index 16bda91..5f9a149 100644 --- a/tests/structs.c +++ b/tests/structs.c @@ -31,15 +31,8 @@ struct Person { char* name; int age; }; -struct Color { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; -}; -struct Shader { - int id; -}; +; +; static const uint8_t sui_bitmap_Point[] = { 0, 0 }; static const TypeInfo sui_typeinfo_Point = { @@ -53,25 +46,15 @@ static const TypeInfo sui_typeinfo_Person = { .pointer_count = 1, .pointer_bitmap = sui_bitmap_Person }; -static const uint8_t sui_bitmap_Color[] = { 0, 0, 0, 0 }; -static const TypeInfo sui_typeinfo_Color = { - .field_count = 4, - .pointer_count = 0, - .pointer_bitmap = sui_bitmap_Color -}; -static const uint8_t sui_bitmap_Shader[] = { 0 }; -static const TypeInfo sui_typeinfo_Shader = { - .field_count = 1, - .pointer_count = 0, - .pointer_bitmap = sui_bitmap_Shader -}; + + int suic_main(void); int suic_main(void) { - struct Point* p = (struct Point){ .x = 5, .y = 10 }; - struct Person* person = (struct Person){ .name = suic_alloc_array(NULL, sizeof(char), 6, "Alice"), .age = 30 }; + struct Point* p = suic_alloc_struct(&sui_typeinfo_Point, sizeof(struct Point), &((struct Point){ .x = 5, .y = 10 })); + struct Person* person = suic_alloc_struct(&sui_typeinfo_Person, sizeof(struct Person), &((struct Person){ .name = suic_alloc_array(NULL, sizeof(char), 6, "Alice"), .age = 30 })); int _ = ((*p).x + (*person).age); return 0; } diff --git a/tests/structs.o b/tests/structs.o index 13e75a4d04c0605447165bb5dbd853e2a8485c53..81c4f34053f523ca95404b32640eff07fe7270d7 100755 GIT binary patch literal 75936 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 literal 45488 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 Qmw$Szl(<+KYE%Ay0sU8fb^rhX From 6f198b17a98b58375f21c7abfe69945f4ccabdb5 Mon Sep 17 00:00:00 2001 From: Masashi Date: Sat, 20 Dec 2025 04:00:59 +0530 Subject: [PATCH 4/4] unsafe --- game/client | Bin 34456 -> 43752 bytes game/client.c | 1482 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 1156 insertions(+), 326 deletions(-) diff --git a/game/client b/game/client index 673d436fc8da0fddcf31bc3c5d26e67f38e74558..989692a8d348427eb311a9e24d0731cf620da34b 100755 GIT binary patch literal 43752 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 Y(|)xmeqnG>{dtc|iO;7gq@l+D0{Wm;4gdfE literal 34456 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 oIsPzQZ|W{f%KPGbBz(G`! +#include #include -#include -#include #include -void* gc_alloc(const TypeInfo* type, size_t size); -void gc_init(void); -void gc_shutdown(void); +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#pragma comment(lib, "ws2_32.lib") +typedef int socklen_t; +#define CLOSESOCK closesocket +#else +#include +#include +#include +#include +#include +#define CLOSESOCK close +#endif -// Helper for allocating arrays -static void* suic_alloc_array(const TypeInfo* type, size_t elem_size, size_t len, void* init_data) { - void* ptr = gc_alloc(type, elem_size * len); - if (init_data) memcpy(ptr, init_data, elem_size * len); - return ptr; +#define PROTOCOL_VERSION 67 +#define SERVER_PORT 27015 +#define MAX_PLAYERS 16 +#define USERNAME_MAX 16 + +#define WEAPON_PISTOL 0 +#define WEAPON_RIFLE 1 + +#define ITEM_NONE 0 +#define ITEM_MEDKIT 1 +#define ITEM_AMMO_PISTOL 2 +#define ITEM_AMMO_RIFLE 3 + +#define MAX_ITEMS 64 + +#define BTN_RELOAD (1u << 0) +#define BTN_SWITCH_PISTOL (1u << 1) +#define BTN_SWITCH_RIFLE (1u << 2) +#define BTN_PICK (1u << 3) +#define BTN_USE_MEDKIT (1u << 4) +#define BTN_JUMP (1u << 5) + +typedef struct { + Vector3 direction; // Normalized light direction + Vector3 color; // RGB color (0-1 range) + float intensity; // Light intensity multiplier + float ambientIntensity; // Ambient light strength + float shadowBias; // Shadow bias to prevent z-fighting + float shadowIntensity; // How dark shadows are (0-1) +} DirectionalLight; + +typedef struct { + Shader shader; + int locViewPos; + int locLightDir; + int locLightColor; + int locLightIntensity; + int locAmbientIntensity; + int locTerrainColor; +} TerrainShader; + +static TerrainShader load_terrain_shader(void) { + TerrainShader ts = {0}; + ts.shader = LoadShader("terrain.vs", "terrain.fs"); + + // Get uniform locations + ts.locViewPos = GetShaderLocation(ts.shader, "viewPos"); + ts.locLightDir = GetShaderLocation(ts.shader, "lightDir"); + ts.locLightColor = GetShaderLocation(ts.shader, "lightColor"); + ts.locLightIntensity = GetShaderLocation(ts.shader, "lightIntensity"); + ts.locAmbientIntensity = GetShaderLocation(ts.shader, "ambientIntensity"); + ts.locTerrainColor = GetShaderLocation(ts.shader, "terrainColor"); + + return ts; } -// Helper for allocating structs -static void* suic_alloc_struct(const TypeInfo* type, size_t size, void* init_data) { - void* ptr = gc_alloc(type, size); - if (init_data) memcpy(ptr, init_data, size); - return ptr; +static void unload_terrain_shader(TerrainShader *ts) { + if (ts && ts->shader.id != 0) { + UnloadShader(ts->shader); + ts->shader.id = 0; + } } +static const uint8_t perm[512] = { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, + 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, + 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, + 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, + 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, + 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, + 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, + 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, + 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, + 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, + 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, + 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, + 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, + 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, + 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, + 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, + 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, + 180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, + 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, + 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, + 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, + 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, + 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, + 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, + 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, + 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, + 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, + 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, + 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, + 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, + 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, + 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, + 156, 180}; -struct DirectionalLight { - struct Vector3* direction; - struct Vector3* color; - float intensity; - float ambientIntensity; - float shadowBias; - float shadowIntensity; -}; -struct TerrainShader { - int shader; - int locViewPos; - int locLightDir; - int locLightColor; - int locLightIntensity; - int locAmbientIntensity; - int locTerrainColor; -}; -struct MsgType_MSG_HELLO { -}; -struct MsgType_MSG_WELCOME { -}; -struct MsgType_MSG_INPUT { -}; -struct MsgType_MSG_SNAPSHOT { -}; -struct MsgType_MSG_SHOOT { -}; -struct MsgType_MSG_ITEMS { -}; -struct MsgType_MSG_ROOM_STATE { -}; -struct MsgType_union { - struct MsgType_MSG_HELLO msg_hello; - struct MsgType_MSG_WELCOME msg_welcome; - struct MsgType_MSG_INPUT msg_input; - struct MsgType_MSG_SNAPSHOT msg_snapshot; - struct MsgType_MSG_SHOOT msg_shoot; - struct MsgType_MSG_ITEMS msg_items; - struct MsgType_MSG_ROOM_STATE msg_room_state; -}; -struct MsgType { - int discriminant; - struct MsgType_union data; -}; -struct MsgHello { - uint8_t type; - struct u32* protocol; - char* username; -}; -struct MsgWelcome { - uint8_t type; - uint8_t playerId; - struct u32* serverTick; -}; -struct MsgInput { - uint8_t type; - uint8_t playerId; - struct u32* clientTick; - float moveX; - float moveZ; - float yaw; - float pitch; - uint8_t buttons; -}; -struct MsgShoot { - uint8_t type; - uint8_t playerId; - struct u32* clientTick; -}; -struct PlayerStateNet { - uint8_t id; - uint8_t alive; - struct i16* hp; - float x; - float y; - float z; - float yaw; - float pitch; - uint8_t weapon; - struct i16* pistolMag; - struct i16* rifleMag; - struct i16* pistolAmmo; - struct i16* rifleAmmo; - struct i16* medkits; - struct i16* reloadTimeLeft; - char* username; -}; -struct MsgSnapshot { - uint8_t type; - struct u32* serverTick; - uint8_t count; - struct PlayerStateNet** p; -}; -struct ItemNet { - struct u16* id; - uint8_t type; - struct i16* qty; - float x; - float y; - float z; -}; -struct MsgItems { - uint8_t type; - struct u32* serverTick; - uint8_t count; - struct ItemNet** items; -}; -struct MsgRoomState { - uint8_t type; - uint8_t state; - float countdownRemaining; - uint8_t winnerId; - char* winnerName; -}; -struct RemotePlayer { - int present; - int alive; - int hp; - struct Vector3* pos; - struct Vector3* prevPos; - float yaw; - float pitch; - uint8_t weapon; - struct i16* pistolMag; - struct i16* rifleMag; - struct i16* pistolAmmo; - struct i16* rifleAmmo; - struct i16* medkits; - struct i16* reloadTimeLeft; - char* username; -}; -struct WorldItem { - int present; - struct u16* id; - uint8_t type; - struct i16* qty; - struct Vector3* pos; -}; - -static const uint8_t sui_bitmap_DirectionalLight[] = { 1, 1, 0, 0, 0, 0 }; -static const TypeInfo sui_typeinfo_DirectionalLight = { - .field_count = 6, - .pointer_count = 2, - .pointer_bitmap = sui_bitmap_DirectionalLight -}; -static const uint8_t sui_bitmap_TerrainShader[] = { 0, 0, 0, 0, 0, 0, 0 }; -static const TypeInfo sui_typeinfo_TerrainShader = { - .field_count = 7, - .pointer_count = 0, - .pointer_bitmap = sui_bitmap_TerrainShader -}; -static const uint8_t sui_bitmap_MsgType_MSG_HELLO[] = { }; -static const TypeInfo sui_typeinfo_MsgType_MSG_HELLO = { - .field_count = 0, - .pointer_count = 0, - .pointer_bitmap = sui_bitmap_MsgType_MSG_HELLO -}; -static const uint8_t sui_bitmap_MsgType_MSG_WELCOME[] = { }; -static const TypeInfo sui_typeinfo_MsgType_MSG_WELCOME = { - .field_count = 0, - .pointer_count = 0, - .pointer_bitmap = sui_bitmap_MsgType_MSG_WELCOME -}; -static const uint8_t sui_bitmap_MsgType_MSG_INPUT[] = { }; -static const TypeInfo sui_typeinfo_MsgType_MSG_INPUT = { - .field_count = 0, - .pointer_count = 0, - .pointer_bitmap = sui_bitmap_MsgType_MSG_INPUT -}; -static const uint8_t sui_bitmap_MsgType_MSG_SNAPSHOT[] = { }; -static const TypeInfo sui_typeinfo_MsgType_MSG_SNAPSHOT = { - .field_count = 0, - .pointer_count = 0, - .pointer_bitmap = sui_bitmap_MsgType_MSG_SNAPSHOT -}; -static const uint8_t sui_bitmap_MsgType_MSG_SHOOT[] = { }; -static const TypeInfo sui_typeinfo_MsgType_MSG_SHOOT = { - .field_count = 0, - .pointer_count = 0, - .pointer_bitmap = sui_bitmap_MsgType_MSG_SHOOT -}; -static const uint8_t sui_bitmap_MsgType_MSG_ITEMS[] = { }; -static const TypeInfo sui_typeinfo_MsgType_MSG_ITEMS = { - .field_count = 0, - .pointer_count = 0, - .pointer_bitmap = sui_bitmap_MsgType_MSG_ITEMS -}; -static const uint8_t sui_bitmap_MsgType_MSG_ROOM_STATE[] = { }; -static const TypeInfo sui_typeinfo_MsgType_MSG_ROOM_STATE = { - .field_count = 0, - .pointer_count = 0, - .pointer_bitmap = sui_bitmap_MsgType_MSG_ROOM_STATE -}; -static const uint8_t sui_bitmap_MsgType_union[] = { 1, 1, 1, 1, 1, 1, 1 }; -static const TypeInfo sui_typeinfo_MsgType_union = { - .field_count = 7, - .pointer_count = 7, - .pointer_bitmap = sui_bitmap_MsgType_union -}; -static const uint8_t sui_bitmap_MsgType[] = { 0, 1 }; -static const TypeInfo sui_typeinfo_MsgType = { - .field_count = 2, - .pointer_count = 1, - .pointer_bitmap = sui_bitmap_MsgType -}; -static const uint8_t sui_bitmap_MsgHello[] = { 0, 1, 1 }; -static const TypeInfo sui_typeinfo_MsgHello = { - .field_count = 3, - .pointer_count = 2, - .pointer_bitmap = sui_bitmap_MsgHello -}; -static const uint8_t sui_bitmap_MsgWelcome[] = { 0, 0, 1 }; -static const TypeInfo sui_typeinfo_MsgWelcome = { - .field_count = 3, - .pointer_count = 1, - .pointer_bitmap = sui_bitmap_MsgWelcome -}; -static const uint8_t sui_bitmap_MsgInput[] = { 0, 0, 1, 0, 0, 0, 0, 0 }; -static const TypeInfo sui_typeinfo_MsgInput = { - .field_count = 8, - .pointer_count = 1, - .pointer_bitmap = sui_bitmap_MsgInput -}; -static const uint8_t sui_bitmap_MsgShoot[] = { 0, 0, 1 }; -static const TypeInfo sui_typeinfo_MsgShoot = { - .field_count = 3, - .pointer_count = 1, - .pointer_bitmap = sui_bitmap_MsgShoot -}; -static const uint8_t sui_bitmap_PlayerStateNet[] = { 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1 }; -static const TypeInfo sui_typeinfo_PlayerStateNet = { - .field_count = 16, - .pointer_count = 8, - .pointer_bitmap = sui_bitmap_PlayerStateNet -}; -static const uint8_t sui_bitmap_MsgSnapshot[] = { 0, 1, 0, 0 }; -static const TypeInfo sui_typeinfo_MsgSnapshot = { - .field_count = 4, - .pointer_count = 1, - .pointer_bitmap = sui_bitmap_MsgSnapshot -}; -static const uint8_t sui_bitmap_ItemNet[] = { 1, 0, 1, 0, 0, 0 }; -static const TypeInfo sui_typeinfo_ItemNet = { - .field_count = 6, - .pointer_count = 2, - .pointer_bitmap = sui_bitmap_ItemNet -}; -static const uint8_t sui_bitmap_MsgItems[] = { 0, 1, 0, 0 }; -static const TypeInfo sui_typeinfo_MsgItems = { - .field_count = 4, - .pointer_count = 1, - .pointer_bitmap = sui_bitmap_MsgItems -}; -static const uint8_t sui_bitmap_MsgRoomState[] = { 0, 0, 0, 0, 1 }; -static const TypeInfo sui_typeinfo_MsgRoomState = { - .field_count = 5, - .pointer_count = 1, - .pointer_bitmap = sui_bitmap_MsgRoomState -}; -static const uint8_t sui_bitmap_RemotePlayer[] = { 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1 }; -static const TypeInfo sui_typeinfo_RemotePlayer = { - .field_count = 15, - .pointer_count = 9, - .pointer_bitmap = sui_bitmap_RemotePlayer -}; -static const uint8_t sui_bitmap_WorldItem[] = { 0, 1, 0, 1, 1 }; -static const TypeInfo sui_typeinfo_WorldItem = { - .field_count = 5, - .pointer_count = 3, - .pointer_bitmap = sui_bitmap_WorldItem -}; - -char* item_name(uint8_t t); -struct Color* apply_directional_lighting(struct Color* baseColor, struct Vector3* normal, struct DirectionalLight* light); -int suic_main(void); - - -char* item_name(uint8_t t) { - return ((t == 1) ? suic_alloc_array(NULL, sizeof(char), 7, "Medkit") : ((t == 2) ? suic_alloc_array(NULL, sizeof(char), 12, "Pistol ammo") : ((t == 3) ? suic_alloc_array(NULL, sizeof(char), 11, "Rifle ammo") : suic_alloc_array(NULL, sizeof(char), 2, "-")))); +static float grad2(int hash, float x, float y) { + int h = hash & 7; + float u = h < 4 ? x : y; + float v = h < 4 ? y : x; + return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f * v : 2.0f * v); } -struct Color* apply_directional_lighting(struct Color* baseColor, struct Vector3* normal, struct DirectionalLight* light) { - struct Vector3* lightDir = (*light).direction; - float diff = fmaxf(0.000000, -((((*lightDir).x * (*normal).x) + ((*lightDir).y * (*normal).y)) + ((*lightDir).z * (*normal).z))); - float brightness = ((*light).ambientIntensity + ((diff * (*light).intensity) * (1.000000 - (*light).ambientIntensity))); - uint8_t r = (uint8_t) ((float) (*baseColor).r * brightness); - uint8_t g = (uint8_t) ((float) (*baseColor).g * brightness); - uint8_t b = (uint8_t) ((float) (*baseColor).b * brightness); - return suic_alloc_struct(&sui_typeinfo_Color, sizeof(struct Color), &(struct Color){ .r = r, .g = g, .b = b, .a = (*baseColor).a }); +static float simplex_noise_2d(float x, float y) { + const float F2 = 0.366025403f; + const float G2 = 0.211324865f; + float s = (x + y) * F2; + int i = (int)floorf(x + s); + int j = (int)floorf(y + s); + float t = (i + j) * G2; + float X0 = i - t, Y0 = j - t; + float x0 = x - X0, y0 = y - Y0; + int i1 = (x0 > y0) ? 1 : 0; + int j1 = (x0 > y0) ? 0 : 1; + float x1 = x0 - i1 + G2, y1 = y0 - j1 + G2; + float x2 = x0 - 1.0f + 2.0f * G2, y2 = y0 - 1.0f + 2.0f * G2; + int ii = i & 255, jj = j & 255; + float n0 = 0.0f, n1 = 0.0f, n2 = 0.0f; + float t0 = 0.5f - x0 * x0 - y0 * y0; + if (t0 >= 0.0f) { + t0 *= t0; + n0 = t0 * t0 * grad2(perm[ii + perm[jj]], x0, y0); + } + float t1 = 0.5f - x1 * x1 - y1 * y1; + if (t1 >= 0.0f) { + t1 *= t1; + n1 = t1 * t1 * grad2(perm[ii + i1 + perm[jj + j1]], x1, y1); + } + float t2 = 0.5f - x2 * x2 - y2 * y2; + if (t2 >= 0.0f) { + t2 *= t2; + n2 = t2 * t2 * grad2(perm[ii + 1 + perm[jj + 1]], x2, y2); + } + return 45.0f * (n0 + n1 + n2); } -int suic_main(void) { - int sw = 1280; - int sh = 720; - suic_init_window(sw, sh, suic_alloc_array(NULL, sizeof(char), 23, "Voxel Shooter - Client")); - suic_set_target_fps(120); - suic_disable_cursor(); - while ((suic_window_should_close() == false)) { - suic_begin_drawing(); - suic_clear_background(135, 206, 235, 255); - suic_draw_text(suic_alloc_array(NULL, sizeof(char), 14, "Hello Suicmez"), 10, 10, 20, 255, 255, 255, 255); - draw_fps(10, 40); - suic_end_drawing(); +static float fbm_noise(float x, float y, int octaves) { + float value = 0.0f, amplitude = 1.0f, frequency = 1.0f, max_value = 0.0f; + for (int i = 0; i < octaves; i++) { + value += simplex_noise_2d(x * frequency, y * frequency) * amplitude; + max_value += amplitude; + amplitude *= 0.5f; + frequency *= 2.0f; + } + return value / max_value; +} + +float get_terrain_height(float x, float z) { + float height = fbm_noise(x * 0.05f, z * 0.05f, 2); + height += fbm_noise(x * 0.01f, z * 0.01f, 2) * 1.5f; + return height * 4.0f + 2.0f; +} + +#define TERRAIN_SIZE 256 +#define TERRAIN_SCALE 1.0f +#define TERRAIN_MIN (-TERRAIN_SIZE * TERRAIN_SCALE / 2.0f) +#define TERRAIN_MAX (TERRAIN_SIZE * TERRAIN_SCALE / 2.0f) + +static Mesh generate_terrain_mesh(void) { + int size = TERRAIN_SIZE; + Mesh mesh = {0}; + + int vertexCount = size * size; + int triangleCount = (size - 1) * (size - 1) * 2; + + mesh.vertexCount = vertexCount; + mesh.triangleCount = triangleCount; + + mesh.vertices = (float *)MemAlloc(vertexCount * 3 * sizeof(float)); + mesh.texcoords = (float *)MemAlloc(vertexCount * 2 * sizeof(float)); + mesh.normals = (float *)MemAlloc(vertexCount * 3 * sizeof(float)); + mesh.indices = + (unsigned short *)MemAlloc(triangleCount * 3 * sizeof(unsigned short)); + + // Generate vertices + for (int z = 0; z < size; z++) { + for (int x = 0; x < size; x++) { + int idx = z * size + x; + float wx = ((float)x - size / 2.0f) * TERRAIN_SCALE; + float wz = ((float)z - size / 2.0f) * TERRAIN_SCALE; + float wy = get_terrain_height(wx, wz); + + mesh.vertices[idx * 3 + 0] = wx; + mesh.vertices[idx * 3 + 1] = wy; + mesh.vertices[idx * 3 + 2] = wz; + + mesh.texcoords[idx * 2 + 0] = (float)x / (float)(size - 1); + mesh.texcoords[idx * 2 + 1] = (float)z / (float)(size - 1); } - suic_close_window(); - return 0; + } + + // Generate indices + int triIdx = 0; + for (int z = 0; z < size - 1; z++) { + for (int x = 0; x < size - 1; x++) { + int i0 = z * size + x; + int i1 = z * size + (x + 1); + int i2 = (z + 1) * size + x; + int i3 = (z + 1) * size + (x + 1); + + mesh.indices[triIdx * 3 + 0] = i0; + mesh.indices[triIdx * 3 + 1] = i2; + mesh.indices[triIdx * 3 + 2] = i1; + triIdx++; + + mesh.indices[triIdx * 3 + 0] = i1; + mesh.indices[triIdx * 3 + 1] = i2; + mesh.indices[triIdx * 3 + 2] = i3; + triIdx++; + } + } + + // Calculate normals using tangent plane approximation from terrain gradients + // This preserves terrain curvature better than simple triangle averaging + for (int z = 0; z < size; z++) { + for (int x = 0; x < size; x++) { + int idx = z * size + x; + float wx = ((float)x - size / 2.0f) * TERRAIN_SCALE; + float wz = ((float)z - size / 2.0f) * TERRAIN_SCALE; + + // Sample height gradients to compute terrain normal + // Use neighboring vertices for finite difference approximation + float h_right = (x + 1 < size) + ? mesh.vertices[(z * size + (x + 1)) * 3 + 1] + : mesh.vertices[idx * 3 + 1]; + float h_left = (x - 1 >= 0) ? mesh.vertices[(z * size + (x - 1)) * 3 + 1] + : mesh.vertices[idx * 3 + 1]; + float h_down = (z + 1 < size) + ? mesh.vertices[((z + 1) * size + x) * 3 + 1] + : mesh.vertices[idx * 3 + 1]; + float h_up = (z - 1 >= 0) ? mesh.vertices[((z - 1) * size + x) * 3 + 1] + : mesh.vertices[idx * 3 + 1]; + + // Compute finite differences + float dh_dx = (h_right - h_left) / (2.0f * TERRAIN_SCALE); + float dh_dz = (h_down - h_up) / (2.0f * TERRAIN_SCALE); + + // Normal from height field: (-dh/dx, 1, -dh/dz) then normalized + float nx = -dh_dx; + float ny = 1.0f; + float nz = -dh_dz; + + float len = sqrtf(nx * nx + ny * ny + nz * nz); + if (len > 0.0001f) { + mesh.normals[idx * 3 + 0] = nx / len; + mesh.normals[idx * 3 + 1] = ny / len; + mesh.normals[idx * 3 + 2] = nz / len; + } else { + mesh.normals[idx * 3 + 0] = 0; + mesh.normals[idx * 3 + 1] = 1; + mesh.normals[idx * 3 + 2] = 0; + } + } + } + + UploadMesh(&mesh, false); + return mesh; } -int main(int argc, char* argv[]) { - // Initialize GC - gc_init(); - // init globals - // init event loop - int result = suic_main(); - // Shutdown GC - gc_shutdown(); - return result; +// apply_directional_lighting: Basic Lambertian diffuse lighting +// Applies directional light with diffuse component based on surface normal +static Color apply_directional_lighting(Color baseColor, Vector3 normal, + DirectionalLight light) { + // Normalize light direction (it's already normalized, but for safety) + Vector3 lightDir = light.direction; + + // Calculate diffuse component: dot product of negative light direction and + // surface normal We use negative because light travels opposite to its + // direction vector + float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y + + lightDir.z * normal.z)); + + // Combine diffuse with ambient light + // Ambient provides minimum brightness even in shadow + float brightness = light.ambientIntensity + + (diff * light.intensity * (1.0f - light.ambientIntensity)); + + // Apply brightness multiplier to base color + int r = (int)(baseColor.r * brightness); + int g = (int)(baseColor.g * brightness); + int b = (int)(baseColor.b * brightness); + + // Clamp RGB to valid range [0, 255] + if (r > 255) + r = 255; + if (g > 255) + g = 255; + if (b > 255) + b = 255; + + return (Color){r, g, b, baseColor.a}; } +// calculate_shadow_factor: Distance-based shadow softening +// Objects at higher elevations or farther from ground get softer, less +// pronounced shadows This simulates how shadows fade with distance and +// atmospheric scattering +static float calculate_shadow_factor(Vector3 worldPos, Vector3 lightDir, + float maxShadowDistance) { + // Calculate height above ground (approximate) + float distFromLight = fmaxf(0.0f, worldPos.y - 2.0f); + + // Fade out shadow strength with distance (max 15% darkening) + float shadowIntensity = + fmaxf(0.0f, 1.0f - (distFromLight / maxShadowDistance)); + return 1.0f - (shadowIntensity * 0.15f); +} + +// calculate_temporal_shadow: Time-based shadow variance for anti-aliasing +// Simulates subtle shadow movement to avoid banding artifacts +static float calculate_temporal_shadow(Vector3 worldPos, float timePhase) { + // Add subtle time-based variation to shadow boundaries + float noiseVal = + sinf(worldPos.x * 0.5f + timePhase) * cosf(worldPos.z * 0.5f + timePhase); + return 1.0f + (noiseVal * 0.02f); // Very subtle variation +} + +// apply_lighting_with_shadows: Full lighting calculation with shadows +// Combines directional light, ambient light, shadows, and temporal variation +static Color apply_lighting_with_shadows(Color baseColor, Vector3 normal, + Vector3 worldPos, + DirectionalLight light) { + // Calculate base shadow factor (distance-based) + float shadowFactor = + calculate_shadow_factor(worldPos, light.direction, 10.0f); + + // Apply shadow to intensity + float adjustedIntensity = light.intensity * shadowFactor; + + // Calculate diffuse component with adjusted intensity + Vector3 lightDir = light.direction; + float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y + + lightDir.z * normal.z)); + + // Combine with ambient using adjusted intensity + float brightness = light.ambientIntensity + (diff * adjustedIntensity * + (1.0f - light.ambientIntensity)); + + // Apply brightness to base color + int r = (int)(baseColor.r * brightness); + int g = (int)(baseColor.g * brightness); + int b = (int)(baseColor.b * brightness); + + // Clamp values to valid RGB range + if (r > 255) + r = 255; + if (g > 255) + g = 255; + if (b > 255) + b = 255; + + return (Color){r, g, b, baseColor.a}; +} + +static Vector3 v3_normalize(Vector3 v) { + float len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); + if (len < 0.0001f) + return (Vector3){0, 1, 0}; + return (Vector3){v.x / len, v.y / len, v.z / len}; +} + +static Vector3 v3(float x, float y, float z) { + Vector3 v = {x, y, z}; + return v; +} +static Vector3 v3_add(Vector3 a, Vector3 b) { + return v3(a.x + b.x, a.y + b.y, a.z + b.z); +} +static Vector3 v3_sub(Vector3 a, Vector3 b) { + return v3(a.x - b.x, a.y - b.y, a.z - b.z); +} +static Vector3 v3_mul(Vector3 a, float s) { + return v3(a.x * s, a.y * s, a.z * s); +} +static float v3_dot(Vector3 a, Vector3 b) { + return a.x * b.x + a.y * b.y + a.z * b.z; +} +static float v3_len(Vector3 a) { + return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z); +} +static Vector3 v3_norm(Vector3 a) { + float l = v3_len(a); + if (l <= 1e-6f) + return v3(0, 0, 1); + return v3(a.x / l, a.y / l, a.z / l); +} + +// calculate_ambient_occlusion: Estimates how occluded a point is based on +// terrain curvature Samples heights in cardinal directions and computes horizon +// angle to terrain Returns occlusion factor from 0 (fully occluded) to 1 (fully +// lit) +static float calculate_ambient_occlusion(Vector3 worldPos, Vector3 normal) { + // AO based on terrain curvature: check if terrain rises around the point + float sampleRadius = 3.0f; + float aoAccum = 0.0f; + int numSamples = 8; + + float centerHeight = worldPos.y; + + // Sample 8 directions around the point + for (int i = 0; i < numSamples; i++) { + float angle = (2.0f * 3.14159265f * (float)i) / (float)numSamples; + float sx = worldPos.x + cosf(angle) * sampleRadius; + float sz = worldPos.z + sinf(angle) * sampleRadius; + float sh = get_terrain_height(sx, sz); + + // Check if terrain is higher relative to surface normal + // Higher terrain in shadow-casting areas reduces occlusion + float heightDiff = sh - centerHeight; + if (heightDiff > 0.1f) { + // Terrain is higher, contributes to shadow + float aoAmount = fminf(1.0f, heightDiff / 2.0f); + aoAccum += aoAmount; + } + } + + float aoFactor = + 1.0f - (aoAccum / (float)numSamples) * 0.6f; // 60% max occlusion + return fmaxf(0.2f, aoFactor); // Min 20% brightness +} + +// apply_phong_lighting_per_pixel: Advanced Phong lighting with per-pixel +// normals Includes diffuse, specular highlight, and ambient occlusion for +// geometry detail +static Color apply_phong_lighting_per_pixel(Color baseColor, Vector3 normal, + Vector3 worldPos, Vector3 camPos, + DirectionalLight light) { + // Normalize inputs + Vector3 lightDir = v3_normalize(light.direction); + Vector3 normal_norm = v3_normalize(normal); + + // Compute view direction (from surface to camera) + Vector3 viewDir = v3_normalize(v3_sub(camPos, worldPos)); + + // Diffuse component: Lambertian shading + float diffuse = fmaxf(0.0f, -v3_dot(lightDir, normal_norm)); + + // Specular component: Blinn-Phong specular highlight + Vector3 halfVec = v3_normalize(v3_add( + v3_norm((Vector3){-lightDir.x, -lightDir.y, -lightDir.z}), viewDir)); + float specular = + powf(fmaxf(0.0f, v3_dot(halfVec, normal_norm)), 32.0f) * 0.5f; + + // Ambient occlusion from terrain geometry + float ao = calculate_ambient_occlusion(worldPos, normal_norm); + + // Shadow based on height (distant higher terrain casts softer shadows) + float shadowFactor = + calculate_shadow_factor(worldPos, light.direction, 10.0f); + + // Combine lighting components + float brightness = light.ambientIntensity * ao; + brightness += diffuse * light.intensity * shadowFactor * + (1.0f - light.ambientIntensity) * ao; + brightness += specular * light.intensity * shadowFactor * + 0.6f; // Specular less affected by AO + + brightness = fminf(1.0f, brightness); + + // Apply brightness to base color + int r = (int)(baseColor.r * brightness); + int g = (int)(baseColor.g * brightness); + int b = (int)(baseColor.b * brightness); + + // Clamp to valid RGB range + if (r > 255) + r = 255; + if (g > 255) + g = 255; + if (b > 255) + b = 255; + + return (Color){r, g, b, baseColor.a}; +} + +static void set_nonblocking(int sock) { +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(sock, FIONBIO, &mode); +#else + int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); +#endif +} + +#pragma pack(push, 1) +typedef enum MsgType : uint8_t { + MSG_HELLO = 1, + MSG_WELCOME = 2, + MSG_INPUT = 3, + MSG_SNAPSHOT = 4, + MSG_SHOOT = 5, + MSG_ITEMS = 6, + MSG_ROOM_STATE = 7 +} MsgType; + +typedef struct MsgHello { + uint8_t type; + uint32_t protocol; + char username[USERNAME_MAX]; +} MsgHello; + +typedef struct MsgWelcome { + uint8_t type; + uint8_t playerId; + uint32_t serverTick; +} MsgWelcome; + +typedef struct MsgInput { + uint8_t type; + uint8_t playerId; + uint32_t clientTick; + float moveX, moveZ, yaw, pitch; + uint8_t buttons; +} MsgInput; + +typedef struct MsgShoot { + uint8_t type; + uint8_t playerId; + uint32_t clientTick; +} MsgShoot; + +typedef struct PlayerStateNet { + uint8_t id, alive; + int16_t hp; + float x, y, z, yaw, pitch; + uint8_t weapon; + int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; + int16_t reloadTimeLeft; + char username[USERNAME_MAX]; +} PlayerStateNet; + +typedef struct MsgSnapshot { + uint8_t type; + uint32_t serverTick; + uint8_t count; + PlayerStateNet p[MAX_PLAYERS]; +} MsgSnapshot; + +typedef struct ItemNet { + uint16_t id; + uint8_t type; + int16_t qty; + float x, y, z; +} ItemNet; + +typedef struct MsgItems { + uint8_t type; + uint32_t serverTick; + uint8_t count; + ItemNet items[MAX_ITEMS]; +} MsgItems; + +typedef struct MsgRoomState { + uint8_t type; + uint8_t state; + float countdownRemaining; + uint8_t winnerId; + char winnerName[USERNAME_MAX]; +} MsgRoomState; +#pragma pack(pop) + +typedef struct RemotePlayer { + int present, alive, hp; + Vector3 pos; + Vector3 prevPos; + float yaw, pitch; + uint8_t weapon; + int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits; + int16_t reloadTimeLeft; + char username[USERNAME_MAX]; +} RemotePlayer; + +typedef struct WorldItem { + int present; + uint16_t id; + uint8_t type; + int16_t qty; + Vector3 pos; +} WorldItem; + +static const char *ItemName(uint8_t t) { + switch (t) { + case ITEM_MEDKIT: + return "Medkit"; + case ITEM_AMMO_PISTOL: + return "Pistol ammo"; + case ITEM_AMMO_RIFLE: + return "Rifle ammo"; + default: + return "-"; + } +} + +static void ui_username_prompt(char outName[USERNAME_MAX]) { + memset(outName, 0, USERNAME_MAX); + while (!WindowShouldClose()) { + int ch = GetCharPressed(); + while (ch > 0) { + int len = (int)strlen(outName); + if (ch >= 32 && ch <= 126) { + if (len < USERNAME_MAX - 1) { + outName[len] = (char)ch; + outName[len + 1] = '\0'; + } + } + ch = GetCharPressed(); + } + if (IsKeyPressed(KEY_BACKSPACE)) { + int len = (int)strlen(outName); + if (len > 0) + outName[len - 1] = '\0'; + } + if (IsKeyPressed(KEY_ENTER) && strlen(outName) > 0) + return; + + BeginDrawing(); + ClearBackground((Color){20, 24, 32, 255}); + DrawText("Enter username (press ENTER):", 60, 80, 28, RAYWHITE); + DrawRectangle(60, 130, 420, 48, (Color){40, 48, 64, 255}); + DrawRectangleLines(60, 130, 420, 48, (Color){120, 140, 170, 255}); + DrawText(outName[0] ? outName : "_", 72, 142, 24, + (Color){230, 230, 240, 255}); + EndDrawing(); + } +} + +int main(void) { +#ifdef _WIN32 + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); +#endif + + const int sw = 1280, sh = 720; + InitWindow(sw, sh, "Voxel Shooter - Client (SMOOTH TERRAIN WITH SHADOWS)"); + SetTargetFPS(120); + + char myName[USERNAME_MAX]; + ui_username_prompt(myName); + + DisableCursor(); + + // Generate terrain mesh + Mesh terrainMesh = generate_terrain_mesh(); + Model terrainModel = LoadModelFromMesh(terrainMesh); + terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = + (Color){80, 140, 70, 255}; + + // Setup directional light + DirectionalLight dirLight = {0}; + dirLight.direction = + v3_normalize(v3(-0.8f, -1.0f, -0.6f)); // Coming from upper-left-back + dirLight.color = v3(1.0f, 1.0f, 1.0f); + dirLight.intensity = 1.2f; + dirLight.ambientIntensity = 0.3f; + dirLight.shadowBias = 0.005f; + dirLight.shadowIntensity = 0.4f; + + // Load terrain shader + TerrainShader terrainShader = load_terrain_shader(); + if (terrainShader.shader.id == 0) { + fprintf( + stderr, + "Warning: Failed to load terrain shader, using default rendering\n"); + // Fallback: Make terrain very bright red to show shader failed + terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = + (Color){255, 100, 100, 255}; + } else { + terrainModel.materials[0].shader = terrainShader.shader; + fprintf(stderr, "Shader loaded successfully!\n"); + } + + int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) { + perror("socket"); + return 1; + } + set_nonblocking(sock); + + struct sockaddr_in srv = {0}; + srv.sin_family = AF_INET; + srv.sin_port = htons(SERVER_PORT); + inet_pton(AF_INET, "127.0.0.1", &srv.sin_addr); + + uint8_t myId = 255; + uint32_t clientTick = 0; + + RemotePlayer rp[MAX_PLAYERS] = {0}; + WorldItem wi[MAX_ITEMS] = {0}; + + // Room state + int roomState = 0; + float countdownRemaining = 0.0f; + char winnerName[USERNAME_MAX] = {0}; + + Vector3 camPos = v3(0, 5.0f, 6); + float yaw = 0.0f, pitch = 0.0f; + + Vector3 prevBodyPos = v3(0, 5.0f, 6); + Vector3 currentTargetBody = v3(0, 5.0f, 6); + float interpTimer = 0.0f; + + float recoilYaw = 0.0f, recoilPitch = 0.0f, crossSpread = 0.0f; + int scoped = 0; + float fireCooldown = 0.0f; + int shotsInBurst = 0; + float burstResetTimer = 0.0f; + + const float pistolFireRate = 4.0f, rifleFireRate = 12.0f; + const float recoilReturn = 18.0f, crossReturn = 14.0f; + const float pistolKickPitch = 0.010f, pistolKickYaw = 0.004f; + const float rifleKickPitch = 0.018f, rifleKickYaw = 0.010f; + const float pistolCrossKick = 2.0f, rifleCrossKick = 4.0f; + const float pistolSprayGrow = 0.4f, rifleSprayGrow = 1.2f; + const float burstResetTime = 0.18f; + + MsgHello hello = {0}; + hello.type = MSG_HELLO; + hello.protocol = PROTOCOL_VERSION; + strncpy(hello.username, myName, USERNAME_MAX - 1); + sendto(sock, (const char *)&hello, (int)sizeof(hello), 0, + (const struct sockaddr *)&srv, sizeof(srv)); + + while (!WindowShouldClose()) { + clientTick++; + float dt = GetFrameTime(); + + fireCooldown -= dt; + if (fireCooldown < 0.0f) + fireCooldown = 0.0f; + burstResetTimer -= dt; + if (burstResetTimer <= 0.0f) + shotsInBurst = 0; + + { + float k = 1.0f - expf(-recoilReturn * dt); + recoilYaw += (0.0f - recoilYaw) * k; + recoilPitch += (0.0f - recoilPitch) * k; + } + { + float k = 1.0f - expf(-crossReturn * dt); + crossSpread += (0.0f - crossSpread) * k; + if (crossSpread < 0.01f) + crossSpread = 0.0f; + } + + for (;;) { + uint8_t buf[1400]; + struct sockaddr_in from = {0}; + socklen_t fromLen = sizeof(from); + int n = (int)recvfrom(sock, (char *)buf, (int)sizeof(buf), 0, + (struct sockaddr *)&from, &fromLen); + if (n <= 0) { +#ifdef _WIN32 + if (WSAGetLastError() == WSAEWOULDBLOCK) + break; +#else + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; +#endif + break; + } + + uint8_t type = buf[0]; + if (type == MSG_WELCOME && n >= (int)sizeof(MsgWelcome)) { + MsgWelcome *w = (MsgWelcome *)buf; + myId = w->playerId; + } else if (type == MSG_SNAPSHOT && n >= (int)sizeof(MsgSnapshot)) { + MsgSnapshot *s = (MsgSnapshot *)buf; + for (int i = 0; i < MAX_PLAYERS; i++) + rp[i].present = 0; + + for (int i = 0; i < (int)s->count && i < MAX_PLAYERS; i++) { + PlayerStateNet *ps = &s->p[i]; + if (ps->id >= MAX_PLAYERS) + continue; + + RemotePlayer *p = &rp[ps->id]; + p->prevPos = p->pos; + p->present = 1; + p->alive = ps->alive; + p->hp = ps->hp; + p->pos = v3(ps->x, ps->y, ps->z); + p->yaw = ps->yaw; + p->pitch = ps->pitch; + p->weapon = ps->weapon; + p->pistolMag = ps->pistolMag; + p->rifleMag = ps->rifleMag; + p->pistolAmmo = ps->pistolAmmo; + p->rifleAmmo = ps->rifleAmmo; + p->medkits = ps->medkits; + p->reloadTimeLeft = ps->reloadTimeLeft; + memset(p->username, 0, USERNAME_MAX); + strncpy(p->username, ps->username, USERNAME_MAX - 1); + } + + if (myId != 255 && rp[myId].present) { + prevBodyPos = currentTargetBody; + currentTargetBody = rp[myId].pos; + interpTimer = 0.0f; + } + } else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) { + MsgItems *m = (MsgItems *)buf; + for (int i = 0; i < MAX_ITEMS; i++) + wi[i].present = 0; + + for (int i = 0; i < (int)m->count && i < MAX_ITEMS; i++) { + wi[i].present = 1; + wi[i].id = m->items[i].id; + wi[i].type = m->items[i].type; + wi[i].qty = m->items[i].qty; + wi[i].pos = v3(m->items[i].x, m->items[i].y, m->items[i].z); + } + } else if (type == MSG_ROOM_STATE && n >= (int)sizeof(MsgRoomState)) { + const MsgRoomState *rs = (const MsgRoomState *)buf; + roomState = rs->state; + countdownRemaining = rs->countdownRemaining; + memset(winnerName, 0, USERNAME_MAX); + if (rs->winnerId < 255) { + strncpy(winnerName, rs->winnerName, USERNAME_MAX - 1); + } + } + } + + // Interpolate camera position + if (myId != 255 && rp[myId].present) { + float interpFactor = interpTimer / (1.0f / 20.0f); + if (interpFactor > 1.0f) + interpFactor = 1.0f; + Vector3 interpBody = v3_add(v3_mul(prevBodyPos, 1.0f - interpFactor), + v3_mul(currentTargetBody, interpFactor)); + camPos.x = interpBody.x; + camPos.y = interpBody.y + 1.0f; + camPos.z = interpBody.z + 0.0001f; + + // Prevent camera from clipping into terrain + float terrain_h = get_terrain_height(camPos.x, camPos.z); + camPos.y = fmaxf(camPos.y, terrain_h + 1.5f); + } + interpTimer += dt; + + Vector2 md = GetMouseDelta(); + const float sens = 0.0025f; + yaw -= md.x * sens; + pitch -= md.y * sens; + if (pitch < -1.5f) + pitch = -1.5f; + if (pitch > 1.5f) + pitch = 1.5f; + + float viewYaw = yaw + recoilYaw; + float viewPitch = pitch + recoilPitch; + if (viewPitch < -1.5f) + viewPitch = -1.5f; + if (viewPitch > 1.5f) + viewPitch = 1.5f; + + 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; + + uint8_t buttons = 0; + if (IsKeyPressed(KEY_ONE)) + buttons |= BTN_SWITCH_PISTOL; + if (IsKeyPressed(KEY_TWO)) + buttons |= BTN_SWITCH_RIFLE; + if (IsKeyPressed(KEY_R)) + buttons |= BTN_RELOAD; + if (IsKeyPressed(KEY_F)) + buttons |= BTN_PICK; + if (IsKeyPressed(KEY_H)) + buttons |= BTN_USE_MEDKIT; + if (IsKeyPressed(KEY_SPACE)) + buttons |= BTN_JUMP; + if (IsKeyPressed(KEY_Z)) + scoped = !scoped; + + if (myId != 255) { + MsgInput in = {0}; + in.type = MSG_INPUT; + in.playerId = myId; + in.clientTick = clientTick; + in.moveX = moveX; + in.moveZ = moveZ; + in.yaw = viewYaw; + in.pitch = viewPitch; + in.buttons = buttons; + sendto(sock, (const char *)&in, (int)sizeof(in), 0, + (const struct sockaddr *)&srv, sizeof(srv)); + } + + if (myId != 255 && IsMouseButtonDown(MOUSE_BUTTON_LEFT) && + fireCooldown <= 0.0f && rp[myId].present) { + int wpn = rp[myId].weapon; + float rate = (wpn == WEAPON_PISTOL) ? pistolFireRate : rifleFireRate; + fireCooldown = 1.0f / rate; + + shotsInBurst++; + burstResetTimer = burstResetTime; + + if (wpn == WEAPON_PISTOL) { + recoilPitch += pistolKickPitch; + recoilYaw += + (((float)GetRandomValue(-1000, 1000)) / 1000.0f) * pistolKickYaw; + crossSpread += pistolCrossKick + shotsInBurst * pistolSprayGrow; + } else { + recoilPitch += rifleKickPitch; + recoilYaw += + (((float)GetRandomValue(-1000, 1000)) / 1000.0f) * rifleKickYaw; + crossSpread += rifleCrossKick + shotsInBurst * rifleSprayGrow; + } + + MsgShoot shmsg = {0}; + shmsg.type = MSG_SHOOT; + shmsg.playerId = myId; + shmsg.clientTick = clientTick; + sendto(sock, (const char *)&shmsg, (int)sizeof(shmsg), 0, + (const struct sockaddr *)&srv, sizeof(srv)); + } + + Vector3 forward = v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch), + cosf(viewYaw) * cosf(viewPitch)); + + // Offset camera position: 0.3m in front, 0.2m below + camPos = v3_add(camPos, v3_mul(forward, 0.3f)); + camPos.y -= 0.2f; + + // Ensure camera doesn't clip into terrain after offset + float terrain_h = get_terrain_height(camPos.x, camPos.z); + camPos.y = fmaxf(camPos.y, terrain_h + 1.5f); + + Camera3D cam = {0}; + cam.position = camPos; + cam.target = v3_add(camPos, forward); + cam.up = v3(0, 1, 0); + cam.fovy = scoped ? 30.0f : 75.0f; + cam.projection = CAMERA_PERSPECTIVE; + + BeginDrawing(); + ClearBackground((Color){135, 206, 235, 255}); + + BeginMode3D(cam); + + // Set up shader uniforms for terrain rendering + if (terrainShader.shader.id != 0) { + // Convert light direction to shader format (should be pointing TO the + // light) + float lightDirArray[3] = {-dirLight.direction.x, -dirLight.direction.y, + -dirLight.direction.z}; + float lightColorArray[3] = {dirLight.color.x, dirLight.color.y, + dirLight.color.z}; + float viewPosArray[3] = {camPos.x, camPos.y, camPos.z}; + // Terrain color in 0-1 range (80, 140, 70) / 255 + float terrainColorArray[3] = {80.0f / 255.0f, 140.0f / 255.0f, + 70.0f / 255.0f}; + + // Debug mode: 0=normal lighting, 1=show normals as colors, 2=show AO only + + SetShaderValue(terrainShader.shader, terrainShader.locViewPos, + viewPosArray, SHADER_UNIFORM_VEC3); + SetShaderValue(terrainShader.shader, terrainShader.locLightDir, + lightDirArray, SHADER_UNIFORM_VEC3); + SetShaderValue(terrainShader.shader, terrainShader.locLightColor, + lightColorArray, SHADER_UNIFORM_VEC3); + SetShaderValue(terrainShader.shader, terrainShader.locLightIntensity, + &dirLight.intensity, SHADER_UNIFORM_FLOAT); + SetShaderValue(terrainShader.shader, terrainShader.locAmbientIntensity, + &dirLight.ambientIntensity, SHADER_UNIFORM_FLOAT); + SetShaderValue(terrainShader.shader, terrainShader.locTerrainColor, + terrainColorArray, SHADER_UNIFORM_VEC3); + } + + // Draw terrain with shader + terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = + (Color){80, 140, 70, 255}; + DrawModel(terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE); + + // Draw border + float borderSize = TERRAIN_MAX - TERRAIN_MIN; + DrawCube((Vector3){0, 0, 0}, borderSize, 1000, borderSize, (Color){255, 0, 0, 100}); + + // Items with basic lighting (no shader for now to keep it simple) + for (int i = 0; i < MAX_ITEMS; i++) { + if (!wi[i].present) + continue; + Color ic = (Color){220, 220, 220, 255}; + if (wi[i].type == ITEM_MEDKIT) + ic = (Color){120, 255, 120, 255}; + if (wi[i].type == ITEM_AMMO_PISTOL) + ic = (Color){255, 220, 120, 255}; + if (wi[i].type == ITEM_AMMO_RIFLE) + ic = (Color){255, 180, 120, 255}; + + // Apply basic lighting to items + Vector3 itemNormal = v3(0, 1, 0); + Color litColor = + apply_lighting_with_shadows(ic, itemNormal, wi[i].pos, dirLight); + DrawSphere(wi[i].pos, 0.3f, litColor); + } + + // Players with basic lighting + for (int i = 0; i < MAX_PLAYERS; i++) { + if (!rp[i].present) + continue; + float interpFactor = interpTimer / (1.0f / 20.0f); + if (interpFactor > 1.0f) + interpFactor = 1.0f; + Vector3 p = v3_add(v3_mul(rp[i].prevPos, 1.0f - interpFactor), + v3_mul(rp[i].pos, interpFactor)); + Color c = + (i == myId) ? (Color){80, 180, 255, 255} : (Color){255, 80, 80, 255}; + if (!rp[i].alive) + c = (Color){120, 120, 120, 255}; + + // Apply basic lighting to players + Vector3 playerNormal = v3(0, 1, 0); + Color litPlayerColor = + apply_lighting_with_shadows(c, playerNormal, p, dirLight); + DrawCapsule(v3(p.x, p.y - 0.5f, p.z), v3(p.x, p.y + 0.5f, p.z), 0.35f, 8, + 8, litPlayerColor); + } + + EndMode3D(); + + // Nameplates + for (int i = 0; i < MAX_PLAYERS; i++) { + if (!rp[i].present || !rp[i].username[0]) + continue; + float interpFactor = interpTimer / (1.0f / 20.0f); + if (interpFactor > 1.0f) + interpFactor = 1.0f; + Vector3 p = v3_add(v3_mul(rp[i].prevPos, 1.0f - interpFactor), + v3_mul(rp[i].pos, interpFactor)); + Vector3 head = v3(p.x, p.y + 1.2f, p.z); + Vector3 camForward = v3_norm(v3_sub(cam.target, cam.position)); + Vector3 toHead = v3_sub(head, cam.position); + if (v3_dot(camForward, toHead) <= 0.0f) + continue; + Vector2 s = GetWorldToScreen(head, cam); + if (s.x < -200 || s.x > sw + 200 || s.y < -200 || s.y > sh + 200) + continue; + int fontSize = 18; + int w = MeasureText(rp[i].username, fontSize); + Color tc = (i == myId) ? (Color){180, 230, 255, 255} : RAYWHITE; + DrawText(rp[i].username, (int)(s.x - w / 2), (int)(s.y - fontSize), + fontSize, tc); + } + + // HUD + if (myId == 255 || !rp[myId].present) { + DrawText("Connecting...", 10, 10, 20, RAYWHITE); + } else { + DrawRectangle(10, 10, 280, 110, (Color){0, 0, 0, 120}); + DrawText("HP", 20, 20, 20, RAYWHITE); + int healthBarWidth = (rp[myId].hp * 220) / 100; + Color healthColor = (rp[myId].hp > 60) ? (Color){80, 255, 80, 120} + : (rp[myId].hp > 30) ? (Color){255, 200, 80, 120} + : (Color){255, 80, 80, 120}; + DrawRectangle(20, 45, 220, 20, (Color){40, 40, 40, 255}); + DrawRectangle(20, 45, healthBarWidth, 20, healthColor); + DrawText(TextFormat("%d", rp[myId].hp), 250, 47, 18, RAYWHITE); + DrawText(TextFormat("Medkits: %d (H to use)", rp[myId].medkits), 20, 75, + 18, + (rp[myId].medkits > 0) ? (Color){120, 255, 120, 120} + : (Color){120, 120, 120, 120}); + DrawText("1=Pistol 2=Rifle R=Reload F=Pick SPACE=Jump", 20, 95, 12, + (Color){150, 150, 150, 150}); + + const char *weaponName = + (rp[myId].weapon == WEAPON_PISTOL) ? "PISTOL" : "RIFLE"; + Color weaponColor = (rp[myId].weapon == WEAPON_PISTOL) + ? (Color){100, 200, 255, 255} + : (Color){255, 150, 100, 255}; + int currentMag = (rp[myId].weapon == WEAPON_PISTOL) ? rp[myId].pistolMag + : rp[myId].rifleMag; + int reserveAmmo = (rp[myId].weapon == WEAPON_PISTOL) ? rp[myId].pistolAmmo + : rp[myId].rifleAmmo; + + DrawRectangle(sw - 260, sh - 120, 250, 110, (Color){0, 0, 0, 180}); + DrawText(weaponName, sw - 250, sh - 110, 28, weaponColor); + DrawText(TextFormat("%d", currentMag), sw - 250, sh - 75, 40, RAYWHITE); + DrawText(TextFormat("/ %d", reserveAmmo), sw - 140, sh - 65, 24, + (Color){180, 180, 180, 255}); + if (rp[myId].reloadTimeLeft > 0) + DrawText("RELOADING...", sw - 250, sh - 30, 20, + (Color){255, 200, 80, 255}); + else if (currentMag == 0) + DrawText("RELOAD!", sw - 250, sh - 30, 20, (Color){255, 80, 80, 255}); + } + + DrawFPS(sw - 90, 10); + + // Room state overlay + if (roomState == 0) { // Waiting + DrawRectangle(sw / 2 - 150, sh / 2 - 50, 350, 100, (Color){0, 0, 0, 200}); + DrawText("WAITING FOR PLAYERS", sw / 2 - 120, sh / 2 - 30, 24, RAYWHITE); + DrawText("Need at least 2 players", sw / 2 - 100, sh / 2 - 5, 18, + (Color){200, 200, 200, 255}); + } else if (roomState == 1) { // Counting down + DrawRectangle(sw / 2 - 150, sh / 2 - 50, 350, 100, (Color){0, 0, 0, 200}); + DrawText("GAME STARTING SOON", sw / 2 - 120, sh / 2 - 30, 24, + (Color){255, 255, 80, 255}); + DrawText(TextFormat("%.1f seconds", countdownRemaining), sw / 2 - 60, + sh / 2 - 5, 20, RAYWHITE); + } else if (roomState == 3) { // Finished + DrawRectangle(sw / 2 - 200, sh / 2 - 50, 450, 100, (Color){0, 0, 0, 200}); + DrawText("GAME FINISHED", sw / 2 - 80, sh / 2 - 30, 28, + (Color){255, 80, 80, 255}); + if (winnerName[0]) { + DrawText(TextFormat("Winner: %s", winnerName), sw / 2 - 100, sh / 2 - 5, + 24, (Color){255, 255, 80, 255}); + } else { + DrawText("No winner", sw / 2 - 50, sh / 2 - 5, 24, RAYWHITE); + } + } + + // Crosshair + { + int cx = sw / 2, cy = sh / 2; + int gap = scoped ? 2 : 6 + (int)crossSpread; + int len = scoped ? 5 : 10, thick = 2; + Color col = (Color){240, 240, 245, 220}; + DrawRectangle(cx - gap - len, cy - thick / 2, len, thick, col); + DrawRectangle(cx + gap, cy - thick / 2, len, thick, col); + DrawRectangle(cx - thick / 2, cy - gap - len, thick, len, col); + DrawRectangle(cx - thick / 2, cy + gap, thick, len, col); + DrawCircleLines(cx, cy, scoped ? 1.5f : 3.0f, + (Color){240, 240, 245, 160}); + } + + EndDrawing(); + } + + UnloadModel(terrainModel); + unload_terrain_shader(&terrainShader); + CloseWindow(); + CLOSESOCK(sock); +#ifdef _WIN32 + WSACleanup(); +#endif + return 0; +}