From f8abae63b303bd44b04751f52608caa5a4817a50 Mon Sep 17 00:00:00 2001 From: Masashi Date: Fri, 19 Dec 2025 21:16:38 +0530 Subject: [PATCH 1/3] fixed arrays --- src/ast.rs | 3 + src/c_ir.rs | 2 + src/c_lowerer/declaration_transpiler.rs | 4 ++ src/c_lowerer/statements_transpiler.rs | 13 +++++ src/codegen/transpiler.rs | 18 +++++- src/lambda_lower.rs | 7 +++ src/monomorphize.rs | 20 +++++-- src/parser.rs | 72 +++++++++++++++++------- src/typechecker.rs | 17 +++++- tests/fixed_arrays.c | 47 ++++++++++++++++ tests/fixed_arrays.o | Bin 0 -> 75696 bytes tests/fixed_arrays.sui | 6 ++ 12 files changed, 183 insertions(+), 26 deletions(-) create mode 100644 tests/fixed_arrays.c create mode 100755 tests/fixed_arrays.o create mode 100644 tests/fixed_arrays.sui diff --git a/src/ast.rs b/src/ast.rs index c4008c5..6e694f2 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -11,6 +11,7 @@ pub enum TypeAnnot { Function(Vec, Box), Tuple(Vec), Array(Box), + FixedArray(Box, i64), Ptr(Box), } @@ -208,6 +209,7 @@ pub enum ExprKind { Bool(bool), String(String), Array(Vec), + FixedArray(Box, i64), Tuple(Vec), StructLit(String, Vec<(String, Expr)>), // Name { a: expr, b: expr } @@ -402,6 +404,7 @@ pub enum TypedExprKind { Bool(bool), String(String), Array(Vec), + FixedArray(Box, usize), Tuple(Vec), StructLit(String, Vec<(String, TypedExpr)>), EnumLit(String, String, Vec), diff --git a/src/c_ir.rs b/src/c_ir.rs index 15e5ea0..44c0638 100644 --- a/src/c_ir.rs +++ b/src/c_ir.rs @@ -9,6 +9,7 @@ pub enum CType { Struct(String), UnnamedStruct(Vec), Array(Box), // heap-allocated array wrapper with data pointer, len, capacity + FixedArray(Box, usize), // fixed-size array like int[10] Func(Vec, Box), // args and return } @@ -48,6 +49,7 @@ impl CType { "struct sui_array_{}", inner.to_string().replace(" ", "_").replace("*", "ptr") ), + CType::FixedArray(inner, size) => format!("{}*", inner.to_string()), // For now, treat as pointer in type strings CType::Func(args, ret) => { let arg_strs: Vec = args.iter().map(|t| t.to_string()).collect(); format!("{} (*)({})", ret.to_string(), arg_strs.join(", ")) diff --git a/src/c_lowerer/declaration_transpiler.rs b/src/c_lowerer/declaration_transpiler.rs index 5cb7dae..7d8a0bb 100644 --- a/src/c_lowerer/declaration_transpiler.rs +++ b/src/c_lowerer/declaration_transpiler.rs @@ -148,6 +148,10 @@ impl DeclarationTranspiler { // Arrays are pointers to the element type Ok(CType::Ptr(Box::new(inner_type))) } + Some(TypeAnnot::FixedArray(inner, size)) => { + let inner_type = self.type_annot_to_c_type(&Some(*inner.clone()))?; + Ok(CType::FixedArray(Box::new(inner_type), *size as usize)) + } Some(TypeAnnot::Tuple(fields)) => { let mut c_fields = Vec::new(); for (i, field_annot) in fields.iter().enumerate() { diff --git a/src/c_lowerer/statements_transpiler.rs b/src/c_lowerer/statements_transpiler.rs index fdf35c5..6d27754 100644 --- a/src/c_lowerer/statements_transpiler.rs +++ b/src/c_lowerer/statements_transpiler.rs @@ -117,6 +117,11 @@ impl StatementsTranspiler { .collect::, _>>()?; Ok(CExpr::ArrayLit(c_exprs)) } + TypedExprKind::FixedArray(expr, size) => { + let c_expr = self.transpile_expr(expr)?; + let c_exprs = vec![c_expr; *size]; + Ok(CExpr::ArrayLit(c_exprs)) + } TypedExprKind::Cast(expr, type_annot) => { let c_expr = self.transpile_expr(expr)?; // Simplified: assuming we can map type annotations to C types @@ -482,6 +487,10 @@ impl StatementsTranspiler { // Arrays are pointers to the element type Ok(CType::Ptr(Box::new(inner_type))) } + Type::FixedArray(inner, size) => { + let inner_type = self.type_to_ctype(inner)?; + Ok(CType::FixedArray(Box::new(inner_type), *size)) + } Type::Struct(name, _) => Ok(CType::Ptr(Box::new(CType::Struct(name.clone())))), Type::Enum(name, _) => Ok(CType::Ptr(Box::new(CType::Struct(name.clone())))), Type::Tuple(types) => { @@ -536,6 +545,10 @@ impl StatementsTranspiler { // Arrays are pointers to the element type Ok(CType::Ptr(Box::new(inner_type))) } + TypeAnnot::FixedArray(inner, size) => { + let inner_type = self.type_annot_to_ctype(inner)?; + Ok(CType::FixedArray(Box::new(inner_type), *size as usize)) + } TypeAnnot::Tuple(fields) => { let mut c_fields = Vec::new(); for (i, field_annot) in fields.iter().enumerate() { diff --git a/src/codegen/transpiler.rs b/src/codegen/transpiler.rs index d3ab12b..e830f36 100644 --- a/src/codegen/transpiler.rs +++ b/src/codegen/transpiler.rs @@ -404,9 +404,23 @@ impl Transpiler { } fn generate_var_decl(&self, var: &CVarDecl) -> String { - let mut output = format!("{} {}", var.ty.to_string(), var.name); + let mut output = match &var.ty { + CType::FixedArray(elem_ty, size) => format!("{} {}[{}]", elem_ty.to_string(), var.name, size), + _ => format!("{} {}", var.ty.to_string(), var.name), + }; if let Some(init) = &var.initializer { - output.push_str(&format!(" = {}", self.generate_expr(init))); + match (&var.ty, init) { + (CType::FixedArray(_, _), CExpr::ArrayLit(elements)) => { + let vec: Vec = elements + .iter() + .map(|expr| self.generate_expr(expr)) + .collect(); + output.push_str(&format!(" = {{{}}}", vec.join(", "))); + } + _ => { + output.push_str(&format!(" = {}", self.generate_expr(init))); + } + } } output } diff --git a/src/lambda_lower.rs b/src/lambda_lower.rs index b9996b1..97e25c7 100644 --- a/src/lambda_lower.rs +++ b/src/lambda_lower.rs @@ -126,6 +126,9 @@ impl LambdaLowerer { self.collect_free_vars_expr(expr, lambda_params, free_vars, local_scope); } } + ExprKind::FixedArray(expr, _) => { + self.collect_free_vars_expr(expr, lambda_params, free_vars, local_scope); + } ExprKind::Tuple(exprs) => { for expr in exprs { self.collect_free_vars_expr(expr, lambda_params, free_vars, local_scope); @@ -340,6 +343,10 @@ impl LambdaLowerer { .collect::, _>>()?; ExprKind::Array(lowered_exprs) } + ExprKind::FixedArray(expr, size) => { + let lowered_expr = self.lower_expr(expr)?; + ExprKind::FixedArray(Box::new(lowered_expr), *size) + } ExprKind::Tuple(exprs) => { let lowered_exprs = exprs .iter() diff --git a/src/monomorphize.rs b/src/monomorphize.rs index fb6c6ac..dd47187 100644 --- a/src/monomorphize.rs +++ b/src/monomorphize.rs @@ -342,6 +342,11 @@ impl Monomorphizer { } TypedExprKind::Array(new_elems) } + TypedExprKind::FixedArray(expr, size) => { + let (new_expr, expr_needs) = self.monomorphize_expr(expr)?; + needs.extend(expr_needs); + TypedExprKind::FixedArray(Box::new(new_expr), *size) + } TypedExprKind::Tuple(elems) => { let mut new_elems = Vec::new(); @@ -767,6 +772,10 @@ impl Monomorphizer { let new_inner = self.substitute_in_type_annot(inner, subst_map)?; Ok(TypeAnnot::Array(Box::new(new_inner))) } + TypeAnnot::FixedArray(inner, size) => { + let new_inner = self.substitute_in_type_annot(inner, subst_map)?; + Ok(TypeAnnot::FixedArray(Box::new(new_inner), *size)) + } TypeAnnot::Ptr(inner) => { let new_inner = self.substitute_in_type_annot(inner, subst_map)?; Ok(TypeAnnot::Ptr(Box::new(new_inner))) @@ -782,6 +791,7 @@ impl Monomorphizer { Type::String => TypeAnnot::Var("string".to_string()), Type::Unit => TypeAnnot::Tuple(Vec::new()), Type::Array(inner) => TypeAnnot::Array(Box::new(self.type_to_type_annot(inner))), + Type::FixedArray(inner, size) => TypeAnnot::FixedArray(Box::new(self.type_to_type_annot(inner)), *size as i64), Type::Ptr(inner) => TypeAnnot::Ptr(Box::new(self.type_to_type_annot(inner))), Type::Tuple(types) => { let annots = types.iter().map(|t| self.type_to_type_annot(t)).collect(); @@ -901,6 +911,9 @@ impl Monomorphizer { TypeAnnot::Array(inner) => { self.collect_needs_from_type(inner, needs); } + TypeAnnot::FixedArray(inner, _) => { + self.collect_needs_from_type(inner, needs); + } TypeAnnot::Ptr(inner) => { self.collect_needs_from_type(inner, needs); } @@ -932,10 +945,8 @@ impl Monomorphizer { let tys = types.iter().map(|t| self.type_annot_to_type(t)).collect(); Type::Tuple(tys) } - TypeAnnot::Array(inner) => { - let inner_type = self.type_annot_to_type(inner); - Type::Array(Box::new(inner_type)) - } + TypeAnnot::Array(inner) => Type::Array(Box::new(self.type_annot_to_type(inner))), + TypeAnnot::FixedArray(inner, size) => Type::FixedArray(Box::new(self.type_annot_to_type(inner)), *size as usize), TypeAnnot::Ptr(inner) => { let inner_type = self.type_annot_to_type(inner); Type::Ptr(Box::new(inner_type)) @@ -1216,6 +1227,7 @@ fn has_typevars_in_type_annot(ty: &TypeAnnot) -> bool { } TypeAnnot::Tuple(types) => types.iter().any(has_typevars_in_type_annot), TypeAnnot::Array(inner) => has_typevars_in_type_annot(inner), + TypeAnnot::FixedArray(inner, _) => has_typevars_in_type_annot(inner), TypeAnnot::Ptr(inner) => has_typevars_in_type_annot(inner), } } diff --git a/src/parser.rs b/src/parser.rs index 0f87e82..9d9e9c2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -912,8 +912,23 @@ impl Parser { } Some((Token::LBracket, _)) => { let inner = self.parse_type_annot()?; - self.expect(Token::RBracket)?; - TypeAnnot::Array(Box::new(inner)) + if matches!(self.peek(), Some(Token::Semicolon)) { + self.next(); + let size = match self.next() { + Some((Token::Int(n), _)) => n, + Some((_, span)) => { + return self.error("Expected integer size for fixed array".to_string(), span); + } + None => { + return self.error("Expected integer size for fixed array".to_string(), start..start); + } + }; + self.expect(Token::RBracket)?; + TypeAnnot::FixedArray(Box::new(inner), size) + } else { + self.expect(Token::RBracket)?; + TypeAnnot::Array(Box::new(inner)) + } } Some((Token::Bang, _)) => TypeAnnot::Cons("never".to_string(), vec![]), Some((_, span)) => { @@ -1607,25 +1622,44 @@ impl Parser { } Some(Token::LBracket) => { self.next(); - let mut elements = Vec::new(); - loop { - if matches!(self.peek(), Some(Token::RBracket)) { - self.next(); - break; - } - elements.push(self.parse_expr()?); - - if matches!(self.peek(), Some(Token::Comma)) { - self.next(); + let first_expr = self.parse_expr()?; + if matches!(self.peek(), Some(Token::Semicolon)) { + self.next(); + let size = match self.next() { + Some((Token::Int(n), _)) => n, + Some((_, span)) => { + return self.error("Expected integer size for fixed array".to_string(), span); + } + None => { + return self.error("Expected integer size for fixed array".to_string(), start..start); + } + }; + self.expect(Token::RBracket)?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::FixedArray(Box::new(first_expr), size), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } else { + let mut elements = vec![first_expr]; + loop { + if matches!(self.peek(), Some(Token::RBracket)) { + self.next(); + break; + } + if matches!(self.peek(), Some(Token::Comma)) { + self.next(); + } + elements.push(self.parse_expr()?); } + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::Array(elements), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) } - - let end = self.peek_span().unwrap_or(start..start).end; - Ok(Expr { - kind: ExprKind::Array(elements), - span: Span::new(&(start..end), self.file.clone()), - attributes: Vec::new(), - }) } Some(Token::KeywordLet) => { self.next(); diff --git a/src/typechecker.rs b/src/typechecker.rs index e1c21fe..9b07ae4 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -12,6 +12,7 @@ pub enum Type { Unit, Never, Array(Box), + FixedArray(Box, usize), Ptr(Box), Tuple(Vec), Function(Vec, Box), @@ -32,6 +33,7 @@ impl Type { Type::Unit => "()".to_string(), Type::Never => "!".to_string(), Type::Array(inner) => format!("[{}]", inner.to_string()), + Type::FixedArray(inner, size) => format!("[{}; {}]", inner.to_string(), size), Type::Ptr(inner) => format!("*{}", inner.to_string()), Type::Tuple(types) => { let type_strs: Vec = types.iter().map(|t| t.to_string()).collect(); @@ -1814,6 +1816,14 @@ impl TypeChecker { ) } + ExprKind::FixedArray(expr, size) => { + let typed_expr = self.typecheck_expr(expr)?; + ( + TypedExprKind::FixedArray(Box::new(typed_expr.clone()), *size as usize), + Type::FixedArray(Box::new(typed_expr.ty), *size as usize), + ) + } + ExprKind::Tuple(elements) => { let mut typed_elements = Vec::new(); let mut types = Vec::new(); @@ -2171,7 +2181,7 @@ impl TypeChecker { } let element_type = match &typed_array.ty { - Type::Array(elem_ty) => (**elem_ty).clone(), + Type::Array(elem_ty) | Type::FixedArray(elem_ty, _) => (**elem_ty).clone(), ty => { return Err(TypeError { kind: TypeErrorKind::NotAnArray(ty.clone()), @@ -2850,6 +2860,9 @@ impl TypeChecker { TypeAnnot::Array(inner) => { Type::Array(Box::new(self.substitute_type(inner, subst_map))) } + TypeAnnot::FixedArray(inner, size) => { + Type::FixedArray(Box::new(self.substitute_type(inner, subst_map)), *size as usize) + } TypeAnnot::Ptr(inner) => Type::Ptr(Box::new(self.substitute_type(inner, subst_map))), TypeAnnot::Tuple(types) => { let substituted_types: Vec = types @@ -2923,6 +2936,7 @@ impl TypeChecker { Type::Tuple(tuple_types) } TypeAnnot::Array(inner) => Type::Array(Box::new(self.type_annot_to_type(inner))), + TypeAnnot::FixedArray(inner, size) => Type::FixedArray(Box::new(self.type_annot_to_type(inner)), *size as usize), TypeAnnot::Ptr(inner) => Type::Ptr(Box::new(self.type_annot_to_type(inner))), } } @@ -2937,6 +2951,7 @@ impl TypeChecker { (Type::Unit, Type::Unit) => true, (Type::Never, _) | (_, Type::Never) => true, (Type::Array(a), Type::Array(b)) => self.types_compatible(a, b), + (Type::FixedArray(a, s1), Type::FixedArray(b, s2)) => s1 == s2 && self.types_compatible(a, b), (Type::Ptr(a), Type::Ptr(b)) => self.types_compatible(a, b), (Type::Tuple(a), Type::Tuple(b)) => { a.len() == b.len() diff --git a/tests/fixed_arrays.c b/tests/fixed_arrays.c new file mode 100644 index 0000000..9d22d90 --- /dev/null +++ b/tests/fixed_arrays.c @@ -0,0 +1,47 @@ +#include "libsuicmez/libsuicmez.h" +#include +#include +#include +#include + +void* gc_alloc(const TypeInfo* type, size_t size); +void gc_init(void); +void gc_shutdown(void); + +// 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; +} + +// 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; +} + + + + +int suic_main(void); + + +int suic_main(void) { + int arr[5] = {42, 42, 42, 42, 42}; + int x = arr[2]; + return x; +} + +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; +} + diff --git a/tests/fixed_arrays.o b/tests/fixed_arrays.o new file mode 100755 index 0000000000000000000000000000000000000000..61598b3548e4eed5ae685009456cda67141f4f5b GIT binary patch literal 75696 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 z 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 literal 0 HcmV?d00001 diff --git a/tests/fixed_arrays.sui b/tests/fixed_arrays.sui new file mode 100644 index 0000000..c233d33 --- /dev/null +++ b/tests/fixed_arrays.sui @@ -0,0 +1,6 @@ +# Fixed array test +fn main() -> int do + let arr: [int; 5] = [42; 5]; + let x = arr[2]; + x +end \ No newline at end of file From dca10507c0cfd451fd792239742f8d53cd4c15ed Mon Sep 17 00:00:00 2001 From: Masashi Date: Sat, 20 Dec 2025 01:55:12 +0530 Subject: [PATCH 2/3] const kw --- game/client.sui | 218 ------------------------ src/ast.rs | 2 + src/c_ir.rs | 2 + src/c_lowerer/declaration_transpiler.rs | 1 + src/c_lowerer/statements_transpiler.rs | 8 + src/lambda_lower.rs | 2 + src/lexer/mod.rs | 11 ++ src/monomorphize.rs | 2 + src/parser.rs | 10 ++ src/typechecker.rs | 29 +++- 10 files changed, 60 insertions(+), 225 deletions(-) diff --git a/game/client.sui b/game/client.sui index 8758452..e69de29 100644 --- a/game/client.sui +++ b/game/client.sui @@ -1,218 +0,0 @@ -Wildered -wildered.da.programmer -Online - -Masashi - - — 5:27 PM -ill add both -Wildered — 5:27 PM -and allow for using variables obv -duh -ii love you -not in the homo way -xd -Masashi - - — 5:27 PM -okok -love u too bro (no homo) -Wildered — 5:28 PM -lol -Masashi - - — 5:32 PM -fuck -nishi's new version of libfishsoup -broke our compiler -:despair: -bitwise added -now arrays -Masashi - - — 5:39 PM -@Wildered -Wildered — 5:40 PM -yes -Masashi - - — 5:40 PM -because of the way our typesystem works -we DONT have null -ull need to do [Option::None; size] -is that okay? -and whatever ur using just make it Option::Some -oh wait -enums dont work -................ -@Wildered what do we do -wait we can do this -say u want an array of type T -u make struct TWrapper -with val: T and is_used: bool -how's the idea -Wildered — 5:45 PM -hi -yes -that's fine -Wildered — 5:46 PM -this is ok -no worries -Masashi - - — 5:46 PM -homemade Option 😭 -Wildered — 5:46 PM -lmao\ -Masashi - - — 5:46 PM -doesnt work -Masashi - - — 5:46 PM -. -Wildered — 5:46 PM -im fine w it -what's the syntax for it -Masashi - - — 5:46 PM -w -fn main() -> int do - let arr: [int; 5] = [42; 5]; - let x = arr[2]; - x -end -Wildered — 5:47 PM -so 42 is the default -Masashi - - — 5:47 PM -correct -Wildered — 5:47 PM -that's brilliant yay -:D -Masashi - - — 5:48 PM -:3 -Masashi - - — 6:47 PM -progress? -Wildered — 6:55 PM -almost done -sneakpeak -Image -Starting import resolution... - [parsing] game/client.sui -Error: Parse error in game/client.sui: Unexpected token in pattern: KeywordElse. Expected variable names, struct patterns like Struct { field }, or enum patterns like Enum::Variant -34 | else - | ~~~~ - | ^ -fn main() -> int do - suic_init_window(1280, 720, "Soup - Voxel Battle Royale"); - suic_enable_msaa_4x(); - let current_screen = 0; - let player_name = "Player"; - let server_host = "127.0.0.1"; -Expand -message.txt -5 KB - -fn main() -> int do - suic_init_window(1280, 720, "Soup - Voxel Battle Royale"); - suic_enable_msaa_4x(); - let current_screen = 0; - let player_name = "Player"; - let server_host = "127.0.0.1"; - let server_port = 8888; - suic_show_fps_meter(suic_vec2 { x: 10.0, y: 10.0 }); - - while suic_window_should_close() == false do - suic_begin_drawing(); - suic_clear_background(30, 30, 40, 255); - - if current_screen == 0 do - suic_draw_text("SOUP", 450, 80, 120, 100, 200, 255, 255); - suic_draw_text("Voxel Battle Royale", 400, 220, 40, 200, 200, 200, 255); - suic_draw_rectangle(450, 320, 380, 80, 50, 150, 255, 255); - suic_draw_rectangle_lines(450, 320, 380, 80, 100, 200, 255, 255); - suic_draw_text("PLAY", 600, 350, 40, 255, 255, 255, 255); - suic_draw_rectangle(450, 430, 380, 80, 100, 100, 100, 255); - suic_draw_rectangle_lines(450, 430, 380, 80, 150, 150, 150, 255); - suic_draw_text("SETTINGS", 540, 460, 40, 255, 255, 255, 255); - suic_draw_rectangle(450, 540, 380, 80, 150, 50, 50, 255); - suic_draw_rectangle_lines(450, 540, 380, 80, 200, 100, 100, 255); - suic_draw_text("QUIT", 600, 570, 40, 255, 255, 255, 255); - suic_draw_text("v0.1.0 - Early Access", 500, 680, 20, 150, 150, 150, 255); - - let mouse_x = suic_get_mouse_x() as int; - let mouse_y = suic_get_mouse_y() as int; - let is_mouse_clicked = suic_is_mouse_button_pressed(MOUSE_BUTTON_LEFT); - - if mouse_x >= 450 && mouse_x <= 830 && mouse_y >= 320 && mouse_y <= 400 && is_mouse_clicked != 0 do - current_screen = 1; - end - - if mouse_x >= 450 && mouse_x <= 830 && mouse_y >= 430 && mouse_y <= 510 && is_mouse_clicked != 0 do - current_screen = 2; - end - - if mouse_x >= 450 && mouse_x <= 830 && mouse_y >= 540 && mouse_y <= 620 && is_mouse_clicked != 0 do - break; - end - end - if current_screen == 1 do - suic_draw_text("MATCHMAKING", 450, 100, 60, 100, 200, 255, 255); - suic_draw_text("Connecting to server...", 400, 250, 30, 200, 200, 200, 255); - suic_draw_text("Host: 127.0.0.1:8888", 400, 310, 20, 150, 150, 150, 255); - suic_draw_text("Your Name: Player", 400, 380, 20, 200, 200, 200, 255); - suic_draw_text("Players in Queue: 0/100", 400, 420, 20, 200, 200, 200, 255); - suic_draw_text("Loading...", 550, 500, 30, 100, 200, 255, 255); - suic_draw_rectangle(100, 630, 150, 60, 150, 50, 50, 255); - suic_draw_rectangle_lines(100, 630, 150, 60, 200, 100, 100, 255); - suic_draw_text("BACK", 120, 650, 30, 255, 255, 255, 255); - - let mouse_x = suic_get_mouse_x() as int; - let mouse_y = suic_get_mouse_y() as int; - let is_mouse_clicked = suic_is_mouse_button_pressed(MOUSE_BUTTON_LEFT); - - if mouse_x >= 100 && mouse_x <= 250 && mouse_y >= 630 && mouse_y <= 690 && is_mouse_clicked != 0 do - current_screen = 0; - end - end - if current_screen == 2 do - suic_draw_text("SETTINGS", 450, 100, 60, 100, 200, 255, 255); - suic_draw_text("Player Name:", 250, 200, 24, 200, 200, 200, 255); - suic_draw_rectangle(450, 190, 300, 50, 50, 50, 50, 255); - suic_draw_rectangle_lines(450, 190, 300, 50, 100, 100, 100, 255); - suic_draw_text("Player", 460, 205, 20, 255, 255, 255, 255); - suic_draw_text("Server Host:", 250, 300, 24, 200, 200, 200, 255); - suic_draw_rectangle(450, 290, 300, 50, 50, 50, 50, 255); - suic_draw_rectangle_lines(450, 290, 300, 50, 100, 100, 100, 255); - suic_draw_text("127.0.0.1", 460, 305, 20, 255, 255, 255, 255); - suic_draw_text("Server Port:", 250, 400, 24, 200, 200, 200, 255); - suic_draw_rectangle(450, 390, 300, 50, 50, 50, 50, 255); - suic_draw_rectangle_lines(450, 390, 300, 50, 100, 100, 100, 255); - suic_draw_text("8888", 460, 405, 20, 255, 255, 255, 255); - suic_draw_rectangle(450, 550, 300, 70, 100, 100, 100, 255); - suic_draw_rectangle_lines(450, 550, 300, 70, 150, 150, 150, 255); - suic_draw_text("BACK TO HOME", 495, 577, 30, 255, 255, 255, 255); - - let mouse_x = suic_get_mouse_x() as int; - let mouse_y = suic_get_mouse_y() as int; - let is_mouse_clicked = suic_is_mouse_button_pressed(MOUSE_BUTTON_LEFT); - - if mouse_x >= 450 && mouse_x <= 750 && mouse_y >= 550 && mouse_y <= 620 && is_mouse_clicked != 0 do - current_screen = 0; - end - end - - suic_end_drawing(); - end - - suic_close_window(); - 0 -end \ No newline at end of file diff --git a/src/ast.rs b/src/ast.rs index 6e694f2..5d413c2 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -205,6 +205,7 @@ pub struct Expr { #[derive(Debug, Clone)] pub enum ExprKind { Int(i64), + TypedInt(i64, String), Float(f64), Bool(bool), String(String), @@ -400,6 +401,7 @@ pub struct TypedExpr { #[derive(Debug, Clone)] pub enum TypedExprKind { Int(i64), + TypedInt(i64, String), Float(f64), Bool(bool), String(String), diff --git a/src/c_ir.rs b/src/c_ir.rs index 44c0638..ccbe882 100644 --- a/src/c_ir.rs +++ b/src/c_ir.rs @@ -2,6 +2,7 @@ pub enum CType { Void, Int, + U8, Float, Bool, Char, @@ -33,6 +34,7 @@ impl CType { match self { CType::Void => "void".to_string(), CType::Int => "int".to_string(), + CType::U8 => "uint8_t".to_string(), CType::Float => "float".to_string(), CType::Bool => "bool".to_string(), CType::Char => "char".to_string(), diff --git a/src/c_lowerer/declaration_transpiler.rs b/src/c_lowerer/declaration_transpiler.rs index 7d8a0bb..27b6d67 100644 --- a/src/c_lowerer/declaration_transpiler.rs +++ b/src/c_lowerer/declaration_transpiler.rs @@ -180,6 +180,7 @@ impl DeclarationTranspiler { pub fn convert_to_c_type(name: &String) -> Result { match String::as_str(name) { "int" => Ok(CType::Int), + "u8" => Ok(CType::U8), "float" => Ok(CType::Float), "bool" => Ok(CType::Bool), "string" => Ok(CType::Ptr(Box::new(CType::Char))), diff --git a/src/c_lowerer/statements_transpiler.rs b/src/c_lowerer/statements_transpiler.rs index 6d27754..41bdf2f 100644 --- a/src/c_lowerer/statements_transpiler.rs +++ b/src/c_lowerer/statements_transpiler.rs @@ -48,6 +48,13 @@ impl StatementsTranspiler { pub fn transpile_expr(&self, expr: &TypedExpr) -> Result { match &expr.kind { TypedExprKind::Int(i) => Ok(CExpr::IntLit(*i)), + TypedExprKind::TypedInt(i, t) => { + let inner = CExpr::IntLit(*i); + match t.as_str() { + "u8" => Ok(CExpr::Cast(Box::new(inner), CType::U8)), + _ => Err(format!("Unsupported typed integer type: {}", t)), + } + } TypedExprKind::Float(f) => Ok(CExpr::FloatLit(*f)), TypedExprKind::Bool(b) => Ok(CExpr::BoolLit(*b)), TypedExprKind::String(s) => Ok(CExpr::StringLit(s.clone())), @@ -477,6 +484,7 @@ impl StatementsTranspiler { fn type_to_ctype(&self, ty: &Type) -> Result { match ty { Type::Int => Ok(CType::Int), + Type::U8 => Ok(CType::U8), Type::Float => Ok(CType::Float), Type::Bool => Ok(CType::Bool), Type::String => Ok(CType::Ptr(Box::new(CType::Char))), diff --git a/src/lambda_lower.rs b/src/lambda_lower.rs index 97e25c7..8d32fc0 100644 --- a/src/lambda_lower.rs +++ b/src/lambda_lower.rs @@ -153,6 +153,7 @@ impl LambdaLowerer { } // Terminal expressions don't contain variables ExprKind::Int(_) + | ExprKind::TypedInt(_, _) | ExprKind::Float(_) | ExprKind::Bool(_) | ExprKind::String(_) @@ -376,6 +377,7 @@ impl LambdaLowerer { } // Terminal expressions that don't contain other expressions ExprKind::Int(_) + | ExprKind::TypedInt(_, _) | ExprKind::Float(_) | ExprKind::Bool(_) | ExprKind::String(_) diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index cb73cf5..ddb1916 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -20,6 +20,14 @@ pub enum Token { }, priority = 4)] Int(i64), + #[regex(r"(0|[1-9][0-9_]*)u8", |lex| { + let s = lex.slice(); + let num_part = &s[..s.len()-2]; + let num = num_part.replace("_", "").parse::().unwrap(); + (num, "u8".to_string()) + }, priority = 5)] + TypedInt((i64, String)), + #[regex(r"(([0-9][0-9_]*\.[0-9_]+|[0-9]*\.[0-9_]+)([eE][+-]?[0-9_]+)?)", |lex| { let s = lex.slice().replace("_", ""); s.parse::().unwrap() @@ -61,6 +69,9 @@ pub enum Token { #[token("string")] KeywordString, + #[token("u8")] + KeywordU8, + #[token("let")] KeywordLet, diff --git a/src/monomorphize.rs b/src/monomorphize.rs index dd47187..ce0d47c 100644 --- a/src/monomorphize.rs +++ b/src/monomorphize.rs @@ -322,6 +322,7 @@ impl Monomorphizer { let mut needs = Vec::new(); let new_kind = match &expr.kind { TypedExprKind::Int(_) + | TypedExprKind::TypedInt(_, _) | TypedExprKind::Float(_) | TypedExprKind::Bool(_) | TypedExprKind::String(_) @@ -786,6 +787,7 @@ impl Monomorphizer { fn type_to_type_annot(&self, ty: &Type) -> TypeAnnot { match ty { Type::Int => TypeAnnot::Var("int".to_string()), + Type::U8 => TypeAnnot::Var("u8".to_string()), Type::Float => TypeAnnot::Var("float".to_string()), Type::Bool => TypeAnnot::Var("bool".to_string()), Type::String => TypeAnnot::Var("string".to_string()), diff --git a/src/parser.rs b/src/parser.rs index 9d9e9c2..9d6fc75 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -883,6 +883,7 @@ impl Parser { Some((Token::KeywordInt, _)) => TypeAnnot::Cons("int".to_string(), vec![]), Some((Token::KeywordFloat, _)) => TypeAnnot::Cons("float".to_string(), vec![]), Some((Token::KeywordString, _)) => TypeAnnot::Cons("string".to_string(), vec![]), + Some((Token::KeywordU8, _)) => TypeAnnot::Cons("u8".to_string(), vec![]), Some((Token::LParen, _)) => { // Check for unit type: () if matches!(self.peek(), Some(Token::RParen)) { @@ -1465,6 +1466,15 @@ impl Parser { attributes: Vec::new(), }) } + Some(Token::TypedInt((n, t))) => { + self.next(); + let end = self.peek_span().unwrap_or(start..start).end; + Ok(Expr { + kind: ExprKind::TypedInt(n, t), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }) + } Some(Token::Float(f)) => { self.next(); let end = self.peek_span().unwrap_or(start..start).end; diff --git a/src/typechecker.rs b/src/typechecker.rs index 9b07ae4..123d0fd 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -6,6 +6,7 @@ use std::fmt; #[derive(Debug, Clone, PartialEq)] pub enum Type { Int, + U8, Float, Bool, String, @@ -27,6 +28,7 @@ impl Type { pub fn to_string(&self) -> String { match self { Type::Int => "int".to_string(), + Type::U8 => "u8".to_string(), Type::Float => "float".to_string(), Type::Bool => "bool".to_string(), Type::String => "string".to_string(), @@ -1763,6 +1765,16 @@ impl TypeChecker { fn typecheck_expr(&mut self, expr: &Expr) -> Result { let (kind, ty) = match &expr.kind { ExprKind::Int(n) => (TypedExprKind::Int(*n), Type::Int), + ExprKind::TypedInt(n, t) => { + let ty = match t.as_str() { + "u8" => Type::U8, + _ => return Err(TypeError { + kind: TypeErrorKind::UndefinedType(t.clone()), + span: expr.span.clone(), + }), + }; + (TypedExprKind::TypedInt(*n, t.clone()), ty) + }, ExprKind::Float(f) => (TypedExprKind::Float(*f), Type::Float), ExprKind::Bool(b) => (TypedExprKind::Bool(*b), Type::Bool), ExprKind::String(s) => (TypedExprKind::String(s.clone()), Type::String), @@ -2846,13 +2858,14 @@ impl TypeChecker { TypeInfoKind::Enum(_) => Type::Enum(name.clone(), substituted_args), } } else { - match name.as_str() { - "int" => Type::Int, - "float" => Type::Float, - "bool" => Type::Bool, - "string" => Type::String, - "unit" => Type::Unit, - "never" => Type::Never, + match name.as_str() { + "int" => Type::Int, + "u8" => Type::U8, + "float" => Type::Float, + "bool" => Type::Bool, + "string" => Type::String, + "unit" => Type::Unit, + "never" => Type::Never, _ => Type::Generic(name.clone(), substituted_args), } } @@ -2906,6 +2919,7 @@ impl TypeChecker { match name.as_str() { "int" => Type::Int, + "u8" => Type::U8, "float" => Type::Float, "bool" => Type::Bool, "string" => Type::String, @@ -2945,6 +2959,7 @@ impl TypeChecker { match (t1, t2) { (Type::Unknown, _) | (_, Type::Unknown) => true, (Type::Int, Type::Int) => true, + (Type::U8, Type::U8) => true, (Type::Float, Type::Float) => true, (Type::Bool, Type::Bool) => true, (Type::String, Type::String) => true, From 22afdb20a4df97a4c5abda6ee1988699b61097d0 Mon Sep 17 00:00:00 2001 From: Masashi Date: Sat, 20 Dec 2025 01:56:02 +0530 Subject: [PATCH 3/3] const kw --- game/client.c | 1482 +++++------------------ game/client.c.bak | 1174 ++++++++++++++++++ src/ast.rs | 18 + src/c_ir.rs | 5 + src/c_lowerer/declaration_transpiler.rs | 7 + src/c_lowerer/statements_transpiler.rs | 11 +- src/codegen/transpiler.rs | 74 +- src/import_resolver.rs | 24 +- src/lexer/mod.rs | 9 + src/main.rs | 6 + src/monomorphize.rs | 6 + src/parser.rs | 67 +- src/typechecker.rs | 360 +++++- 13 files changed, 2055 insertions(+), 1188 deletions(-) create mode 100644 game/client.c.bak diff --git a/game/client.c b/game/client.c index eb6df46..a5a5599 100644 --- a/game/client.c +++ b/game/client.c @@ -1,1174 +1,344 @@ -#include "raylib.h" -#include -#include +#include "libsuicmez/libsuicmez.h" #include +#include +#include #include -#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 +void* gc_alloc(const TypeInfo* type, size_t size); +void gc_init(void); +void gc_shutdown(void); -#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 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; } -static void unload_terrain_shader(TerrainShader *ts) { - if (ts && ts->shader.id != 0) { - UnloadShader(ts->shader); - ts->shader.id = 0; - } +// 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 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}; -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 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 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); +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 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); +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(); } - } - - // 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; + suic_close_window(); + return 0; } -// 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}; +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; } -// 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; -} diff --git a/game/client.c.bak b/game/client.c.bak new file mode 100644 index 0000000..eb6df46 --- /dev/null +++ b/game/client.c.bak @@ -0,0 +1,1174 @@ +#include "raylib.h" +#include +#include +#include +#include + +#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 + +#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; +} + +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}; + +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); +} + +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); +} + +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); + } + } + + // 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; +} + +// 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; +} diff --git a/src/ast.rs b/src/ast.rs index 5d413c2..c0c9bdf 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -67,6 +67,7 @@ pub enum ASTNodeKind { Function(Function), Extern(Extern), Load(Load), + Const(Const), Struct(Struct), Enum(Enum), Impl(Impl), @@ -80,6 +81,13 @@ pub struct Use { pub span: Span, } +#[derive(Debug, Clone)] +pub struct Const { + pub name: String, + pub typ: Option, + pub value: Expr, +} + // ? implies OPTIONAL here // \( implies the presence of (. same for /) @@ -265,6 +273,8 @@ pub enum BinOp { Or, BitwiseAnd, BitwiseOr, + LShift, + RShift, Eq, Neq, Lt, @@ -314,6 +324,7 @@ pub enum TypedASTNodeKind { Function(TypedFunction), Extern(TypedExtern), Load(TypedLoad), + Const(TypedConst), Struct(TypedStruct), Enum(TypedEnum), Impl(TypedImpl), @@ -347,6 +358,13 @@ pub struct TypedLoad { pub span: Span, } +#[derive(Debug, Clone)] +pub struct TypedConst { + pub name: String, + pub typ: Option, + pub value: TypedExpr, +} + #[derive(Debug, Clone)] pub struct TypedStruct { pub name: String, diff --git a/src/c_ir.rs b/src/c_ir.rs index ccbe882..f622f05 100644 --- a/src/c_ir.rs +++ b/src/c_ir.rs @@ -65,6 +65,7 @@ pub struct CVarDecl { pub name: String, pub ty: CType, pub initializer: Option, + pub is_const: bool, } #[derive(Debug, Clone, PartialEq)] @@ -120,6 +121,8 @@ pub enum CBinaryOp { Or, BitwiseAnd, BitwiseOr, + LShift, + RShift, } impl CBinaryOp { @@ -140,6 +143,8 @@ impl CBinaryOp { CBinaryOp::Or => "||", CBinaryOp::BitwiseAnd => "&", CBinaryOp::BitwiseOr => "|", + CBinaryOp::LShift => "<<", + CBinaryOp::RShift => ">>", } } } diff --git a/src/c_lowerer/declaration_transpiler.rs b/src/c_lowerer/declaration_transpiler.rs index 27b6d67..0a987de 100644 --- a/src/c_lowerer/declaration_transpiler.rs +++ b/src/c_lowerer/declaration_transpiler.rs @@ -23,6 +23,7 @@ impl DeclarationTranspiler { name: field.name.clone(), ty: field_type, initializer: None, + is_const: false, }); } @@ -53,6 +54,7 @@ impl DeclarationTranspiler { name: name.clone(), ty: param_type, initializer: None, + is_const: false, }); } @@ -81,6 +83,7 @@ impl DeclarationTranspiler { name: field_name, ty: c_type, initializer: None, + is_const: false, }); } @@ -100,6 +103,7 @@ impl DeclarationTranspiler { name: field_name, ty: CType::Struct(struct_name), initializer: None, + is_const: false, }); } @@ -115,11 +119,13 @@ impl DeclarationTranspiler { name: "discriminant".to_string(), ty: CType::Int, initializer: None, + is_const: false, }, CVarDecl { name: "data".to_string(), ty: CType::Struct(union_name_clone), initializer: None, + is_const: false, }, ]; @@ -160,6 +166,7 @@ impl DeclarationTranspiler { name: format!("field{}", i), ty: field_type, initializer: None, + is_const: false, }); } Ok(CType::UnnamedStruct(c_fields)) diff --git a/src/c_lowerer/statements_transpiler.rs b/src/c_lowerer/statements_transpiler.rs index 41bdf2f..be26df7 100644 --- a/src/c_lowerer/statements_transpiler.rs +++ b/src/c_lowerer/statements_transpiler.rs @@ -30,6 +30,7 @@ impl StatementsTranspiler { name: name.clone(), ty, initializer, + is_const: false, }; (name, decl) @@ -182,6 +183,7 @@ impl StatementsTranspiler { name: name.clone(), ty: c_type, initializer, + is_const: false, }; Ok(CStmt::VarDecl(var_decl)) } @@ -233,6 +235,7 @@ impl StatementsTranspiler { name: var_name.clone(), ty: CType::Int, initializer: Some(start_expr), + is_const: false, }; let cond = CExpr::BinOp( @@ -284,6 +287,7 @@ impl StatementsTranspiler { )), Box::new(CExpr::Var(idx_name.clone())), )), + is_const: false, }); let mut body_stmts = vec![bind]; @@ -333,6 +337,7 @@ impl StatementsTranspiler { Box::new(CExpr::Var(arr_name.clone())), Box::new(CExpr::Var(idx_name.clone())), )), + is_const: false, }); let mut body_stmts = vec![bind]; @@ -468,6 +473,8 @@ impl StatementsTranspiler { BinOp::Or => Ok(CBinaryOp::Or), BinOp::BitwiseAnd => Ok(CBinaryOp::BitwiseAnd), BinOp::BitwiseOr => Ok(CBinaryOp::BitwiseOr), + BinOp::LShift => Ok(CBinaryOp::LShift), + BinOp::RShift => Ok(CBinaryOp::RShift), } } @@ -481,7 +488,7 @@ impl StatementsTranspiler { } } - fn type_to_ctype(&self, ty: &Type) -> Result { + pub fn type_to_ctype(&self, ty: &Type) -> Result { match ty { Type::Int => Ok(CType::Int), Type::U8 => Ok(CType::U8), @@ -509,6 +516,7 @@ impl StatementsTranspiler { name: format!("field{}", i), ty: c_type, initializer: None, + is_const: false, }); } Ok(CType::UnnamedStruct(fields)) @@ -565,6 +573,7 @@ impl StatementsTranspiler { name: format!("field{}", i), ty: field_type, initializer: None, + is_const: false, }); } Ok(CType::UnnamedStruct(c_fields)) diff --git a/src/codegen/transpiler.rs b/src/codegen/transpiler.rs index e830f36..79772fe 100644 --- a/src/codegen/transpiler.rs +++ b/src/codegen/transpiler.rs @@ -90,6 +90,39 @@ impl Transpiler { self.lower_declarations_to_c_ir(node)?; } + // Add builtin structs + if !self.structs.iter().any(|s| s.name == "Color") { + self.structs.push(CStructDecl { + name: "Color".to_string(), + fields: vec![ + CVarDecl { name: "r".to_string(), ty: CType::U8, initializer: None, is_const: false }, + CVarDecl { name: "g".to_string(), ty: CType::U8, initializer: None, is_const: false }, + CVarDecl { name: "b".to_string(), ty: CType::U8, initializer: None, is_const: false }, + CVarDecl { name: "a".to_string(), ty: CType::U8, initializer: None, is_const: false }, + ], + }); + // Generate TypeInfo + let bitmap = Self::generate_pointer_bitmap(&vec![ + CVarDecl { name: "r".to_string(), ty: CType::U8, initializer: None, is_const: false }, + CVarDecl { name: "g".to_string(), ty: CType::U8, initializer: None, is_const: false }, + CVarDecl { name: "b".to_string(), ty: CType::U8, initializer: None, is_const: false }, + CVarDecl { name: "a".to_string(), ty: CType::U8, initializer: None, is_const: false }, + ]); + self.typeinfo_map.insert("Color".to_string(), bitmap); + } + if !self.structs.iter().any(|s| s.name == "Shader") { + self.structs.push(CStructDecl { + name: "Shader".to_string(), + fields: vec![ + CVarDecl { name: "id".to_string(), ty: CType::Int, initializer: None, is_const: false }, + ], + }); + let bitmap = Self::generate_pointer_bitmap(&vec![ + CVarDecl { name: "id".to_string(), ty: CType::Int, initializer: None, is_const: false }, + ]); + self.typeinfo_map.insert("Shader".to_string(), bitmap); + } + self.lower_function_bodies_to_c_ir(nodes)?; // Rename main to suic_main and track that we have a main @@ -238,6 +271,16 @@ impl Transpiler { self.functions.push(func_decl); } } + TypedASTNodeKind::Const(c) => { + let value_code = self.stmt_transpiler.transpile_expr(&c.value)?; + let var_decl = CVarDecl { + name: c.name.clone(), + ty: self.stmt_transpiler.type_to_ctype(&c.value.ty)?, + initializer: Some(value_code), + is_const: true, + }; + self.globals.push(var_decl); + } TypedASTNodeKind::Extern(e) => { // For externs, we might need to add function prototypes // But for now, skip as they're handled differently @@ -408,6 +451,9 @@ impl Transpiler { CType::FixedArray(elem_ty, size) => format!("{} {}[{}]", elem_ty.to_string(), var.name, size), _ => format!("{} {}", var.ty.to_string(), var.name), }; + if var.is_const { + output = format!("const {}", output); + } if let Some(init) = &var.initializer { match (&var.ty, init) { (CType::FixedArray(_, _), CExpr::ArrayLit(elements)) => { @@ -652,13 +698,14 @@ impl Transpiler { "rl_pop_matrix" => "suic_rl_pop_matrix", "rl_translate_f" => "suic_rl_translate_f", "rl_rotate_f" => "suic_rl_rotate_f", - // UI Framework Utils - "draw_text" => "suic_draw_text", - "draw_rectangle" => "suic_draw_rectangle", - "draw_rectangle_lines" => "suic_draw_rectangle_lines", - "measure_text" => "suic_measure_text", - "draw_circle" => "suic_draw_circle", - "draw_line" => "suic_draw_line", + // UI Framework Utils + "draw_text" => "DrawText", + "draw_rectangle" => "DrawRectangle", + "draw_rectangle_lines" => "DrawRectangleLines", + "measure_text" => "MeasureText", + "draw_circle" => "DrawCircle", + "draw_line" => "DrawLine", + "draw_fps" => "DrawFPS", // Player controller functions "player_controller_create" => "suic_player_controller_create", "player_controller_destroy" => "suic_player_controller_destroy", @@ -749,18 +796,7 @@ impl Transpiler { "Vec3" => "suic_vec3".to_string(), _ => format!("struct {}", struct_name), }; - // For built-in simple structs, generate compound literal directly - // For user-defined structs, use heap allocation - match struct_name.as_str() { - "Vec2" | "Vec3" => 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(", ") - ), - } + format!("({}){{ {} }}", 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/src/import_resolver.rs b/src/import_resolver.rs index 47a63ba..4c3a360 100644 --- a/src/import_resolver.rs +++ b/src/import_resolver.rs @@ -370,14 +370,22 @@ impl ImportResolver { ASTNodeKind::Load(_) => { result.push(node); } - ASTNodeKind::Trait(t) => { - self.symbol_registry.register( - t.name.clone(), - filename.to_string(), - node.span.clone(), - )?; - result.push(node); - } + ASTNodeKind::Trait(t) => { + self.symbol_registry.register( + t.name.clone(), + filename.to_string(), + node.span.clone(), + )?; + result.push(node); + } + ASTNodeKind::Const(c) => { + self.symbol_registry.register( + c.name.clone(), + filename.to_string(), + node.span.clone(), + )?; + result.push(node); + } } } diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index ddb1916..f9327bc 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -146,6 +146,9 @@ pub enum Token { #[token("trait")] KeywordTrait, + #[token("const")] + KeywordConst, + // #[token("type")] // KeywordType, // @@ -182,6 +185,12 @@ pub enum Token { #[token("**", priority = 3)] Power, + #[token("<<")] + LShift, + + #[token(">>")] + RShift, + #[token("$")] Dollar, diff --git a/src/main.rs b/src/main.rs index fe23aa1..83cd7c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -244,6 +244,9 @@ fn run_file(filename: &str, debug: bool) -> Result<(), String> { 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()) } @@ -297,6 +300,9 @@ fn run_file(filename: &str, debug: bool) -> Result<(), String> { 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()) } diff --git a/src/monomorphize.rs b/src/monomorphize.rs index ce0d47c..a360ea1 100644 --- a/src/monomorphize.rs +++ b/src/monomorphize.rs @@ -104,6 +104,9 @@ impl Monomorphizer { for (_idx, node) in nodes.iter().enumerate() { match &node.kind { + TypedASTNodeKind::Const(c) => { + result_nodes.push(node.clone()); + } TypedASTNodeKind::Function(f) => { // Skip generic functions - they'll be added as specialized versions when needed if !f.parameters.is_empty() { @@ -988,6 +991,9 @@ fn check_node_for_typevars(node: &TypedASTNode) -> Result<(), MonomorphizationEr TypedASTNodeKind::Function(f) => { check_function_for_typevars(f)?; } + TypedASTNodeKind::Const(c) => { + // Consts should not have typevars + } TypedASTNodeKind::Struct(s) => { check_struct_for_typevars(s)?; } diff --git a/src/parser.rs b/src/parser.rs index 9d6fc75..79eb17a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -149,6 +149,16 @@ impl Parser { attributes, }) } + Some(Token::KeywordConst) => { + self.next(); + let const_def = self.parse_const()?; + let end = self.peek_span().unwrap_or(start..start).end; + Ok(ASTNode { + kind: ASTNodeKind::Const(const_def), + span: Span::new(&(start..end), self.file.clone()), + attributes, + }) + } Some(Token::KeywordStruct) => { self.next(); let struct_def = self.parse_struct()?; @@ -211,12 +221,33 @@ impl Parser { } Some(token) => { let span = self.peek_span().unwrap_or(start..start); - self.error(format!("Unexpected token at top level: {:?}. Expected declarations like 'fn', 'struct', 'enum', 'impl', 'trait', 'use', 'load', or 'extern'", token), span) + self.error(format!("Unexpected token at top level: {:?}. Expected declarations like 'fn', 'struct', 'enum', 'impl', 'trait', 'const', 'use', 'load', or 'extern'", token), span) } None => self.error("Unexpected end of file at top level. Expected declarations like 'fn', 'struct', 'enum', etc.".to_string(), start..start), } } + fn parse_const(&mut self) -> Result { + let start = self.peek_span().unwrap_or(0..0).start; + let name = match self.next() { + Some((Token::Variable(n), _)) => n, + Some((_, span)) => return self.error("Expected constant name after 'const' keyword. Example: const PI = 3.14".to_string(), span), + None => return self.error("Expected constant name after 'const' keyword. Example: const PI = 3.14".to_string(), start..start), + }; + + let typ = if matches!(self.peek(), Some(Token::Colon)) { + self.next(); + Some(self.parse_type_annot()?) + } else { + None + }; + + self.expect(Token::Assign)?; + let value = self.parse_expr()?; + + Ok(Const { name, typ, value }) + } + fn parse_attribute(&mut self) -> Result { self.expect(Token::At)?; let start = self.peek_span().unwrap_or(0..0).start; @@ -1173,14 +1204,44 @@ impl Parser { Ok(left) } - fn parse_bitwise_and_expr(&mut self) -> Result { + fn parse_shift_expr(&mut self) -> Result { let mut left = self.parse_eq_expr()?; + loop { + let op = if matches!(self.peek(), Some(Token::LShift)) { + Some(BinOp::LShift) + } else if matches!(self.peek(), Some(Token::RShift)) { + Some(BinOp::RShift) + } else { + None + }; + + if let Some(op) = op { + let start = left.span.start; + self.next(); + let right = self.parse_eq_expr()?; + let end = right.span.end; + left = Expr { + kind: ExprKind::BinOp(Box::new(left), op, Box::new(right)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }; + } else { + break; + } + } + + Ok(left) + } + + fn parse_bitwise_and_expr(&mut self) -> Result { + let mut left = self.parse_shift_expr()?; + loop { if matches!(self.peek(), Some(Token::BitAnd)) { let start = left.span.start; self.next(); - let right = self.parse_eq_expr()?; + let right = self.parse_shift_expr()?; let end = right.span.end; left = Expr { kind: ExprKind::BinOp(Box::new(left), BinOp::BitwiseAnd, Box::new(right)), diff --git a/src/typechecker.rs b/src/typechecker.rs index 123d0fd..3c22207 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -370,6 +370,351 @@ impl TypeChecker { } fn add_builtin_functions(&mut self) { + // Builtin types + self.env.types.insert( + "Vector3".to_string(), + TypeInfo { + kind: TypeInfoKind::Struct(vec![ + ("x".to_string(), TypeAnnot::Var("float".to_string())), + ("y".to_string(), TypeAnnot::Var("float".to_string())), + ("z".to_string(), TypeAnnot::Var("float".to_string())), + ]), + parameters: vec![], + }, + ); + self.env.types.insert( + "Color".to_string(), + TypeInfo { + kind: TypeInfoKind::Struct(vec![ + ("r".to_string(), TypeAnnot::Var("u8".to_string())), + ("g".to_string(), TypeAnnot::Var("u8".to_string())), + ("b".to_string(), TypeAnnot::Var("u8".to_string())), + ("a".to_string(), TypeAnnot::Var("u8".to_string())), + ]), + parameters: vec![], + }, + ); + self.env.types.insert( + "Shader".to_string(), + TypeInfo { + kind: TypeInfoKind::Struct(vec![ + ("id".to_string(), TypeAnnot::Var("int".to_string())), + ]), + parameters: vec![], + }, + ); + self.env.types.insert( + "Vector2".to_string(), + TypeInfo { + kind: TypeInfoKind::Struct(vec![ + ("x".to_string(), TypeAnnot::Var("float".to_string())), + ("y".to_string(), TypeAnnot::Var("float".to_string())), + ]), + parameters: vec![], + }, + ); + self.env.types.insert( + "sockaddr_in".to_string(), + TypeInfo { + kind: TypeInfoKind::Struct(vec![ + ("sin_family".to_string(), TypeAnnot::Var("u16".to_string())), + ("sin_port".to_string(), TypeAnnot::Var("u16".to_string())), + ("sin_addr".to_string(), TypeAnnot::Cons("in_addr".to_string(), vec![])), + ("sin_zero".to_string(), TypeAnnot::FixedArray(Box::new(TypeAnnot::Var("u8".to_string())), 8)), + ]), + parameters: vec![], + }, + ); + + // Builtin functions + // Raylib + self.env.functions.insert( + "init_window".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int, Type::String], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "close_window".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "window_should_close".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Bool, + }, + ); + self.env.functions.insert( + "set_target_fps".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "disable_cursor".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "get_char_pressed".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "is_key_pressed".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Bool, + }, + ); + self.env.functions.insert( + "is_key_down".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Bool, + }, + ); + self.env.functions.insert( + "get_mouse_delta".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Struct("Vector2".to_string(), vec![]), + }, + ); + self.env.functions.insert( + "is_mouse_button_down".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Bool, + }, + ); + self.env.functions.insert( + "get_random_value".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "get_frame_time".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "begin_drawing".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "end_drawing".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "clear_background".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int, Type::Int, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "draw_text".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::String, Type::Int, Type::Int, Type::Int, Type::Int, Type::Int, Type::Int, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "draw_fps".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int], + return_type: Type::Unit, + }, + ); + + // Math functions + self.env.functions.insert( + "sinf".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "cosf".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "sqrtf".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "fmaxf".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "floorf".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "powf".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "expf".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float], + return_type: Type::Float, + }, + ); + + // Noise functions + self.env.functions.insert( + "grad2".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Float, Type::Float], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "simplex_noise_2d".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "fbm_noise".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float, Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "get_terrain_height".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float], + return_type: Type::Float, + }, + ); + + // Socket functions + self.env.functions.insert( + "socket".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int, Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "sendto".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Ptr(Box::new(Type::U8)), Type::Int, Type::Int, Type::Ptr(Box::new(Type::Unit)), Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "recvfrom".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Ptr(Box::new(Type::U8)), Type::Int, Type::Int, Type::Ptr(Box::new(Type::Unit)), Type::Ptr(Box::new(Type::Int))], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "close".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "htonl".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "htons".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "inet_pton".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::String, Type::Ptr(Box::new(Type::Unit))], + return_type: Type::Int, + }, + ); + // ODE Physics Engine functions self.env.functions.insert( "ode_init".to_string(), @@ -1564,6 +1909,19 @@ impl TypeChecker { fn typecheck_node(&mut self, node: &ASTNode) -> Result { let ty = match &node.kind { + ASTNodeKind::Const(c) => { + let typed_value = self.typecheck_expr(&c.value)?; + return Ok(TypedASTNode { + kind: TypedASTNodeKind::Const(TypedConst { + name: c.name.clone(), + typ: c.typ.clone(), + value: typed_value.clone(), + }), + span: node.span.clone(), + attributes: node.attributes.clone(), + ty: typed_value.ty, + }); + } ASTNodeKind::Function(f) => { let typed_func = self.typecheck_function(f)?; let ty = typed_func.ty.clone(); @@ -1862,7 +2220,7 @@ impl TypeChecker { let typed_right = self.typecheck_expr(right)?; let result_type = match op { - BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::BitwiseAnd | BinOp::BitwiseOr => { + BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod | BinOp::BitwiseAnd | BinOp::BitwiseOr | BinOp::LShift | BinOp::RShift => { if !self.types_compatible(&typed_left.ty, &typed_right.ty) { return Err(TypeError { kind: TypeErrorKind::TypeMismatch(