From 5a60e1c62a9f2b5d8639f2f5afca1f853551d0cb Mon Sep 17 00:00:00 2001 From: Masashi Date: Sat, 20 Dec 2025 17:11:21 +0530 Subject: [PATCH] GAME READY --- compile.sh | 4 +- compiler_out.txt | 222 +++++++ game/client | Bin 0 -> 115344 bytes game/client.c | 393 ++++++++++++ game/client.o | Bin 0 -> 115344 bytes game/client.sui | 447 +++++++++++++- game/inc/suic_app.c | 6 +- game/inc/suic_gameplay.c | 2 +- game/inc/suic_terrain.c | 2 +- game/junk | 457 ++++++++++++++ libsuicmez/libsuicmez.c | 31 + libsuicmez/libsuicmez.h | 23 +- src/c_lowerer/declaration_transpiler.rs | 6 +- src/c_lowerer/statements_transpiler.rs | 24 +- src/codegen/transpiler.rs | 61 +- src/main.rs | 1 + src/parser.rs | 34 +- src/typechecker.rs | 786 +++++++++++++++++++++--- 18 files changed, 2378 insertions(+), 121 deletions(-) create mode 100644 compiler_out.txt create mode 100755 game/client create mode 100644 game/client.c create mode 100755 game/client.o create mode 100644 game/junk diff --git a/compile.sh b/compile.sh index ec128e1..6794d74 100755 --- a/compile.sh +++ b/compile.sh @@ -46,7 +46,7 @@ fi # Get the C file name (should be next to the .sui file) C_FILE="${INPUT_SUI%.sui}.c" -OUTPUT_BINARY="${INPUT_SUI%.sui}.o" +OUTPUT_BINARY="${INPUT_SUI%.sui}" if [ -n "$2" ]; then OUTPUT_BINARY="$2" @@ -67,7 +67,7 @@ ODE_LIBS=$(pkg-config --libs ode 2>/dev/null || echo "-lode -lm") # Compile C to executable with libsuicmez, raylib, and ODE support echo "Compiling C code and linking with libsuicmez, raylib, and ODE..." -gcc $OPTIMIZE_FLAG -I. -Ilibfishsoup/include "$C_FILE" libsuicmez/libsuicmez.c libsuicmez/suicmez_gc.c libfishsoup/src/*.c $RAYLIB_CFLAGS $ODE_CFLAGS -o "$OUTPUT_BINARY" $RAYLIB_LIBS $ODE_LIBS -lz -lm || exit 1 +gcc $OPTIMIZE_FLAG -I. -Ilibfishsoup/include "$C_FILE" libsuicmez/libsuicmez.c libsuicmez/suicmez_gc.c libfishsoup/src/*.c game/inc/suic_math.c game/inc/suic_terrain.c game/inc/suic_net.c game/inc/suic_ui.c game/inc/suic_lighting.c $RAYLIB_CFLAGS $ODE_CFLAGS -o "$OUTPUT_BINARY" $RAYLIB_LIBS $ODE_LIBS -lz -lm || exit 1 echo "✓ Successfully created: $OUTPUT_BINARY" echo " Run with: ./$OUTPUT_BINARY" diff --git a/compiler_out.txt b/compiler_out.txt new file mode 100644 index 0000000..3391e11 --- /dev/null +++ b/compiler_out.txt @@ -0,0 +1,222 @@ +warning: unused variable: `size` + --> src/c_ir.rs:54:38 + | +54 | CType::FixedArray(inner, size) => format!("{}*", inner.to_string()), // For now, treat as pointer in type strings + | ^^^^ help: if this is intentional, prefix it with an underscore: `_size` + | + = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default + +warning: unused variable: `e` + --> src/codegen/transpiler.rs:284:38 + | +284 | TypedASTNodeKind::Extern(e) => { + | ^ help: if this is intentional, prefix it with an underscore: `_e` + +warning: unused variable: `typed_trait` + --> src/codegen/transpiler.rs:289:37 + | +289 | TypedASTNodeKind::Trait(typed_trait) => {} + | ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_typed_trait` + +warning: unreachable pattern + --> src/codegen/transpiler.rs:679:22 + | +667 | "enable_msaa_4x" => "suic_enable_msaa_4x", + | ---------------- matches all the relevant values +... +679 | "enable_msaa_4x" => "suic_enable_msaa_4x", + | ^^^^^^^^^^^^^^^^ no value can reach this + | + = note: `#[warn(unreachable_patterns)]` (part of `#[warn(unused)]`) on by default + +warning: unused variable: `c` + --> src/monomorphize.rs:107:41 + | +107 | TypedASTNodeKind::Const(c) => { + | ^ help: if this is intentional, prefix it with an underscore: `_c` + +warning: unused variable: `c` + --> src/monomorphize.rs:994:33 + | +994 | TypedASTNodeKind::Const(c) => { + | ^ help: if this is intentional, prefix it with an underscore: `_c` + +warning: unused variable: `path` + --> src/typechecker.rs:2638:30 + | +2638 | ASTNodeKind::Use(path) => Type::Unit, + | ^^^^ help: if this is intentional, prefix it with an underscore: `_path` + +warning: unused variable: `is_ptr` + --> src/typechecker.rs:3169:28 + | +3169 | let (name, is_ptr) = match &typed_obj.ty { + | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_is_ptr` + +warning: field `type_map` is never read + --> src/c_lowerer/declaration_transpiler.rs:7:5 + | +6 | pub struct DeclarationTranspiler { + | --------------------- field in this struct +7 | type_map: HashMap, + | ^^^^^^^^ + | + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: associated items `register_array_type`, `get_array_struct_name`, and `generate_heap_alloc` are never used + --> src/codegen/transpiler.rs:63:8 + | + 48 | impl Transpiler { + | --------------- associated items in this implementation +... + 63 | fn register_array_type(&mut self, elem_type: &CType) { + | ^^^^^^^^^^^^^^^^^^^ +... +332 | fn get_array_struct_name(elem_type: &CType) -> String { + | ^^^^^^^^^^^^^^^^^^^^^ +... +482 | fn generate_heap_alloc(&self, ty: &CType) -> String { + | ^^^^^^^^^^^^^^^^^^^ + +warning: field `type_params` is never read + --> src/typechecker.rs:198:5 + | +197 | struct FunctionType { + | ------------ field in this struct +198 | type_params: Vec, + | ^^^^^^^^^^^ + | + = note: `FunctionType` has derived impls for the traits `Debug` and `Clone`, but these are intentionally ignored during dead code analysis + +warning: fields `methods` and `parameters` are never read + --> src/typechecker.rs:205:5 + | +204 | struct TraitInfo { + | --------- fields in this struct +205 | methods: HashMap, + | ^^^^^^^ +206 | parameters: Vec, + | ^^^^^^^^^^ + | + = note: `TraitInfo` has derived impls for the traits `Debug` and `Clone`, but these are intentionally ignored during dead code analysis + +warning: field `trait_name` is never read + --> src/typechecker.rs:212:5 + | +210 | struct ImplInfo { + | -------- field in this struct +211 | target: String, +212 | trait_name: Option, + | ^^^^^^^^^^ + | + = note: `ImplInfo` has derived impls for the traits `Debug` and `Clone`, but these are intentionally ignored during dead code analysis + +warning: method `get_var` is never used + --> src/typechecker.rs:294:8 + | +216 | impl TypeEnv { + | ------------ method in this implementation +... +294 | fn get_var(&self, id: &crate::ast::BindingId) -> Option<&VarInfo> { + | ^^^^^^^ + +warning: `suicmez` (lib) generated 14 warnings + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s + Running `target/debug/suicmez game/client.sui` +Type checking file: game/client.sui + +=== Import Resolution Phase === +Starting import resolution... + [parsing] game/client.sui +Import resolution complete! 41 total nodes loaded +Lambda lowering passed! 41 nodes after lowering. +Type checking passed! 41 nodes typechecked. + +Typed AST nodes before monomorphization: + [0] Struct(SuicVec3) with 0 params + [1] Struct(SuicColor) with 0 params + [2] Struct(SuicDirectionalLight) with 0 params + [3] Const(PISTOL_FIRE_RATE) + [4] Const(RIFLE_FIRE_RATE) + [5] Const(RECOIL_RETURN) + [6] Const(CROSS_RETURN) + [7] Const(PISTOL_KICK_PITCH) + [8] Const(PISTOL_KICK_YAW) + [9] Const(RIFLE_KICK_PITCH) + [10] Const(RIFLE_KICK_YAW) + [11] Const(PISTOL_CROSS_KICK) + [12] Const(RIFLE_CROSS_KICK) + [13] Const(PISTOL_SPRAY_GROW) + [14] Const(RIFLE_SPRAY_GROW) + [15] Const(BURST_RESET_TIME) + [16] Const(SUIC_BTN_RELOAD) + [17] Const(SUIC_BTN_SWITCH_PISTOL) + [18] Const(SUIC_BTN_SWITCH_RIFLE) + [19] Const(SUIC_BTN_PICK) + [20] Const(SUIC_BTN_USE_MEDKIT) + [21] Const(SUIC_BTN_JUMP) + [22] Const(SUIC_WEAPON_PISTOL) + [23] Const(SUIC_WEAPON_RIFLE) + [24] Const(SUIC_ITEM_MEDKIT) + [25] Const(SUIC_ITEM_AMMO_PISTOL) + [26] Const(SUIC_ITEM_AMMO_RIFLE) + [27] Const(SUIC_TERRAIN_SIZE) + [28] Const(SUIC_TERRAIN_SCALE) + [29] Const(SUIC_TERRAIN_MIN) + [30] Const(SUIC_TERRAIN_MAX) + [31] Const(SUIC_NET_MAX_PLAYERS) + [32] Const(SUIC_NET_USERNAME_MAX) + [33] Const(SUIC_NET_MAX_ITEMS) + [34] Struct(GameState) with 0 params + [35] Function(suic_game_init) + [36] Function(suic_game_shutdown) + [37] Function(suic_game_handle_input) + [38] Function(suic_game_update) + [39] Function(suic_game_render) + [40] Function(main) +Monomorphization passed! 41 nodes after specialization. + +Monomorphized AST nodes: + [0] Struct(SuicVec3) with 0 params + [1] Struct(SuicColor) with 0 params + [2] Struct(SuicDirectionalLight) with 0 params + [3] Const(PISTOL_FIRE_RATE) + [4] Const(RIFLE_FIRE_RATE) + [5] Const(RECOIL_RETURN) + [6] Const(CROSS_RETURN) + [7] Const(PISTOL_KICK_PITCH) + [8] Const(PISTOL_KICK_YAW) + [9] Const(RIFLE_KICK_PITCH) + [10] Const(RIFLE_KICK_YAW) + [11] Const(PISTOL_CROSS_KICK) + [12] Const(RIFLE_CROSS_KICK) + [13] Const(PISTOL_SPRAY_GROW) + [14] Const(RIFLE_SPRAY_GROW) + [15] Const(BURST_RESET_TIME) + [16] Const(SUIC_BTN_RELOAD) + [17] Const(SUIC_BTN_SWITCH_PISTOL) + [18] Const(SUIC_BTN_SWITCH_RIFLE) + [19] Const(SUIC_BTN_PICK) + [20] Const(SUIC_BTN_USE_MEDKIT) + [21] Const(SUIC_BTN_JUMP) + [22] Const(SUIC_WEAPON_PISTOL) + [23] Const(SUIC_WEAPON_RIFLE) + [24] Const(SUIC_ITEM_MEDKIT) + [25] Const(SUIC_ITEM_AMMO_PISTOL) + [26] Const(SUIC_ITEM_AMMO_RIFLE) + [27] Const(SUIC_TERRAIN_SIZE) + [28] Const(SUIC_TERRAIN_SCALE) + [29] Const(SUIC_TERRAIN_MIN) + [30] Const(SUIC_TERRAIN_MAX) + [31] Const(SUIC_NET_MAX_PLAYERS) + [32] Const(SUIC_NET_USERNAME_MAX) + [33] Const(SUIC_NET_MAX_ITEMS) + [34] Struct(GameState) with 0 params + [35] Function(suic_game_init) + [36] Function(suic_game_shutdown) + [37] Function(suic_game_handle_input) + [38] Function(suic_game_update) + [39] Function(suic_game_render) + [40] Function(main) +Type variable check passed! No type variables remain in AST. +Error: Code generation error: Unsupported expression: Do([TypedExpr { kind: Let(BindingId(48), "wpn", Default, None, TypedExpr { kind: Call(TypedExpr { kind: Variable("suic_net_player_weapon"), span: Span { start: 5043, end: 5066, file: "game/client.sui" }, attributes: [], ty: Function([Int], Int) }, [TypedExpr { kind: Variable("my_id"), span: Span { start: 5066, end: 5072, file: "game/client.sui" }, attributes: [], ty: Int }]), span: Span { start: 5043, end: 5084, file: "game/client.sui" }, attributes: [], ty: Int }), span: Span { start: 5033, end: 5084, file: "game/client.sui" }, attributes: [], ty: Int }, TypedExpr { kind: Let(BindingId(49), "rate", Mutable, None, TypedExpr { kind: Variable("RIFLE_FIRE_RATE"), span: Span { start: 5096, end: 5122, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5081, end: 5122, file: "game/client.sui" }, attributes: [], ty: Float }, TypedExpr { kind: If(TypedExpr { kind: BinOp(TypedExpr { kind: Variable("wpn"), span: Span { start: 5123, end: 5129, file: "game/client.sui" }, attributes: [], ty: Int }, Eq, TypedExpr { kind: Variable("SUIC_WEAPON_PISTOL"), span: Span { start: 5130, end: 5151, file: "game/client.sui" }, attributes: [], ty: Int }), span: Span { start: 5123, end: 5151, file: "game/client.sui" }, attributes: [], ty: Bool }, TypedExpr { kind: Do([TypedExpr { kind: Assign(TypedExpr { kind: Variable("rate"), span: Span { start: 5164, end: 5170, file: "game/client.sui" }, attributes: [], ty: Float }, TypedExpr { kind: Variable("PISTOL_FIRE_RATE"), span: Span { start: 5171, end: 5199, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5164, end: 5199, file: "game/client.sui" }, attributes: [], ty: Unit }]), span: Span { start: 5149, end: 5218, file: "game/client.sui" }, attributes: [], ty: Unit }, None), span: Span { start: 5120, end: 5218, file: "game/client.sui" }, attributes: [], ty: Unit }, TypedExpr { kind: Assign(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5208, end: 5219, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "fire_cooldown"), span: Span { start: 5208, end: 5234, file: "game/client.sui" }, attributes: [], ty: Float }, TypedExpr { kind: BinOp(TypedExpr { kind: Float(1.0), span: Span { start: 5235, end: 5240, file: "game/client.sui" }, attributes: [], ty: Float }, Div, TypedExpr { kind: Variable("rate"), span: Span { start: 5241, end: 5265, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5235, end: 5265, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5208, end: 5265, file: "game/client.sui" }, attributes: [], ty: Unit }, TypedExpr { kind: Assign(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5255, end: 5266, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "shots_in_burst"), span: Span { start: 5255, end: 5282, file: "game/client.sui" }, attributes: [], ty: Int }, TypedExpr { kind: BinOp(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5283, end: 5294, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "shots_in_burst"), span: Span { start: 5283, end: 5310, file: "game/client.sui" }, attributes: [], ty: Int }, Add, TypedExpr { kind: Int(1), span: Span { start: 5311, end: 5331, file: "game/client.sui" }, attributes: [], ty: Int }), span: Span { start: 5283, end: 5331, file: "game/client.sui" }, attributes: [], ty: Int }), span: Span { start: 5255, end: 5331, file: "game/client.sui" }, attributes: [], ty: Unit }, TypedExpr { kind: Assign(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5321, end: 5332, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "burst_reset_timer"), span: Span { start: 5321, end: 5351, file: "game/client.sui" }, attributes: [], ty: Float }, TypedExpr { kind: Variable("BURST_RESET_TIME"), span: Span { start: 5352, end: 5380, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5321, end: 5380, file: "game/client.sui" }, attributes: [], ty: Unit }, TypedExpr { kind: If(TypedExpr { kind: BinOp(TypedExpr { kind: Variable("wpn"), span: Span { start: 5381, end: 5387, file: "game/client.sui" }, attributes: [], ty: Int }, Eq, TypedExpr { kind: Variable("SUIC_WEAPON_PISTOL"), span: Span { start: 5388, end: 5409, file: "game/client.sui" }, attributes: [], ty: Int }), span: Span { start: 5381, end: 5409, file: "game/client.sui" }, attributes: [], ty: Bool }, TypedExpr { kind: Do([TypedExpr { kind: Assign(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5422, end: 5433, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "recoil_pitch"), span: Span { start: 5422, end: 5447, file: "game/client.sui" }, attributes: [], ty: Float }, TypedExpr { kind: BinOp(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5448, end: 5459, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "recoil_pitch"), span: Span { start: 5448, end: 5473, file: "game/client.sui" }, attributes: [], ty: Float }, Add, TypedExpr { kind: Variable("PISTOL_KICK_PITCH"), span: Span { start: 5474, end: 5514, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5448, end: 5514, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5422, end: 5514, file: "game/client.sui" }, attributes: [], ty: Unit }, TypedExpr { kind: Assign(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5504, end: 5515, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "recoil_yaw"), span: Span { start: 5504, end: 5527, file: "game/client.sui" }, attributes: [], ty: Float }, TypedExpr { kind: BinOp(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5528, end: 5539, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "recoil_yaw"), span: Span { start: 5528, end: 5551, file: "game/client.sui" }, attributes: [], ty: Float }, Add, TypedExpr { kind: BinOp(TypedExpr { kind: BinOp(TypedExpr { kind: Cast(TypedExpr { kind: Call(TypedExpr { kind: Variable("get_random_value"), span: Span { start: 5553, end: 5570, file: "game/client.sui" }, attributes: [], ty: Function([Int, Int], Int) }, [TypedExpr { kind: UnOp(Neg, TypedExpr { kind: Int(1000), span: Span { start: 5571, end: 5576, file: "game/client.sui" }, attributes: [], ty: Int }), span: Span { start: 5570, end: 5576, file: "game/client.sui" }, attributes: [], ty: Int }, TypedExpr { kind: Int(1000), span: Span { start: 5577, end: 5582, file: "game/client.sui" }, attributes: [], ty: Int }]), span: Span { start: 5553, end: 5585, file: "game/client.sui" }, attributes: [], ty: Int }, Cons("float", [])), span: Span { start: 5553, end: 5593, file: "game/client.sui" }, attributes: [], ty: Float }, Div, TypedExpr { kind: Float(1000.0), span: Span { start: 5594, end: 5601, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5552, end: 5603, file: "game/client.sui" }, attributes: [], ty: Float }, Mul, TypedExpr { kind: Variable("PISTOL_KICK_YAW"), span: Span { start: 5604, end: 5642, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5552, end: 5642, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5528, end: 5642, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5504, end: 5642, file: "game/client.sui" }, attributes: [], ty: Unit }, TypedExpr { kind: Assign(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5632, end: 5643, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "cross_spread"), span: Span { start: 5632, end: 5657, file: "game/client.sui" }, attributes: [], ty: Float }, TypedExpr { kind: BinOp(TypedExpr { kind: BinOp(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5658, end: 5669, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "cross_spread"), span: Span { start: 5658, end: 5683, file: "game/client.sui" }, attributes: [], ty: Float }, Add, TypedExpr { kind: Variable("PISTOL_CROSS_KICK"), span: Span { start: 5684, end: 5703, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5658, end: 5703, file: "game/client.sui" }, attributes: [], ty: Float }, Add, TypedExpr { kind: BinOp(TypedExpr { kind: Cast(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5704, end: 5715, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "shots_in_burst"), span: Span { start: 5704, end: 5732, file: "game/client.sui" }, attributes: [], ty: Int }, Cons("float", [])), span: Span { start: 5704, end: 5740, file: "game/client.sui" }, attributes: [], ty: Float }, Mul, TypedExpr { kind: Variable("PISTOL_SPRAY_GROW"), span: Span { start: 5741, end: 5770, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5704, end: 5770, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5658, end: 5770, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5632, end: 5770, file: "game/client.sui" }, attributes: [], ty: Unit }]), span: Span { start: 5407, end: 5781, file: "game/client.sui" }, attributes: [], ty: Unit }, None), span: Span { start: 5378, end: 5781, file: "game/client.sui" }, attributes: [], ty: Unit }, TypedExpr { kind: If(TypedExpr { kind: BinOp(TypedExpr { kind: Variable("wpn"), span: Span { start: 5782, end: 5788, file: "game/client.sui" }, attributes: [], ty: Int }, Eq, TypedExpr { kind: Variable("SUIC_WEAPON_RIFLE"), span: Span { start: 5789, end: 5809, file: "game/client.sui" }, attributes: [], ty: Int }), span: Span { start: 5782, end: 5809, file: "game/client.sui" }, attributes: [], ty: Bool }, TypedExpr { kind: Do([TypedExpr { kind: Assign(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5822, end: 5833, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "recoil_pitch"), span: Span { start: 5822, end: 5847, file: "game/client.sui" }, attributes: [], ty: Float }, TypedExpr { kind: BinOp(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5848, end: 5859, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "recoil_pitch"), span: Span { start: 5848, end: 5873, file: "game/client.sui" }, attributes: [], ty: Float }, Add, TypedExpr { kind: Variable("RIFLE_KICK_PITCH"), span: Span { start: 5874, end: 5913, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5848, end: 5913, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5822, end: 5913, file: "game/client.sui" }, attributes: [], ty: Unit }, TypedExpr { kind: Assign(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5903, end: 5914, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "recoil_yaw"), span: Span { start: 5903, end: 5926, file: "game/client.sui" }, attributes: [], ty: Float }, TypedExpr { kind: BinOp(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 5927, end: 5938, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "recoil_yaw"), span: Span { start: 5927, end: 5950, file: "game/client.sui" }, attributes: [], ty: Float }, Add, TypedExpr { kind: BinOp(TypedExpr { kind: BinOp(TypedExpr { kind: Cast(TypedExpr { kind: Call(TypedExpr { kind: Variable("get_random_value"), span: Span { start: 5952, end: 5969, file: "game/client.sui" }, attributes: [], ty: Function([Int, Int], Int) }, [TypedExpr { kind: UnOp(Neg, TypedExpr { kind: Int(1000), span: Span { start: 5970, end: 5975, file: "game/client.sui" }, attributes: [], ty: Int }), span: Span { start: 5969, end: 5975, file: "game/client.sui" }, attributes: [], ty: Int }, TypedExpr { kind: Int(1000), span: Span { start: 5976, end: 5981, file: "game/client.sui" }, attributes: [], ty: Int }]), span: Span { start: 5952, end: 5984, file: "game/client.sui" }, attributes: [], ty: Int }, Cons("float", [])), span: Span { start: 5952, end: 5992, file: "game/client.sui" }, attributes: [], ty: Float }, Div, TypedExpr { kind: Float(1000.0), span: Span { start: 5993, end: 6000, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5951, end: 6002, file: "game/client.sui" }, attributes: [], ty: Float }, Mul, TypedExpr { kind: Variable("RIFLE_KICK_YAW"), span: Span { start: 6003, end: 6040, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5951, end: 6040, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5927, end: 6040, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 5903, end: 6040, file: "game/client.sui" }, attributes: [], ty: Unit }, TypedExpr { kind: Assign(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 6030, end: 6041, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "cross_spread"), span: Span { start: 6030, end: 6055, file: "game/client.sui" }, attributes: [], ty: Float }, TypedExpr { kind: BinOp(TypedExpr { kind: BinOp(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 6056, end: 6067, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "cross_spread"), span: Span { start: 6056, end: 6081, file: "game/client.sui" }, attributes: [], ty: Float }, Add, TypedExpr { kind: Variable("RIFLE_CROSS_KICK"), span: Span { start: 6082, end: 6100, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 6056, end: 6100, file: "game/client.sui" }, attributes: [], ty: Float }, Add, TypedExpr { kind: BinOp(TypedExpr { kind: Cast(TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 6101, end: 6112, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "shots_in_burst"), span: Span { start: 6101, end: 6129, file: "game/client.sui" }, attributes: [], ty: Int }, Cons("float", [])), span: Span { start: 6101, end: 6137, file: "game/client.sui" }, attributes: [], ty: Float }, Mul, TypedExpr { kind: Variable("RIFLE_SPRAY_GROW"), span: Span { start: 6138, end: 6166, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 6101, end: 6166, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 6056, end: 6166, file: "game/client.sui" }, attributes: [], ty: Float }), span: Span { start: 6030, end: 6166, file: "game/client.sui" }, attributes: [], ty: Unit }]), span: Span { start: 5807, end: 6195, file: "game/client.sui" }, attributes: [], ty: Unit }, None), span: Span { start: 5779, end: 6195, file: "game/client.sui" }, attributes: [], ty: Unit }, TypedExpr { kind: Call(TypedExpr { kind: Variable("suic_net_send_shoot"), span: Span { start: 6176, end: 6196, file: "game/client.sui" }, attributes: [], ty: Function([Int, Int], Unit) }, [TypedExpr { kind: Variable("my_id"), span: Span { start: 6196, end: 6202, file: "game/client.sui" }, attributes: [], ty: Int }, TypedExpr { kind: Dot(TypedExpr { kind: Variable("game_state"), span: Span { start: 6203, end: 6214, file: "game/client.sui" }, attributes: [], ty: Ptr(Struct("GameState", [])) }, "client_tick"), span: Span { start: 6203, end: 6226, file: "game/client.sui" }, attributes: [], ty: Int }]), span: Span { start: 6176, end: 6234, file: "game/client.sui" }, attributes: [], ty: Unit }]) diff --git a/game/client b/game/client new file mode 100755 index 0000000000000000000000000000000000000000..47eb6d0e029e8ad36ec5ae33783c295f2a4a1a6a GIT binary patch literal 115344 zcmeFa4R}=5wKqNiMuU~k1c^oKM^tPBS`8K~2hysEVNFq@nX+99G!RU~h z;~0%r+SJd~+Iv&$N3QiFQjtldOtiEHOKoboc4}#RVrV;6dQB}g@9($vIcN4vGKg*8 z|NH!(=Z1%K&RTozwbx#I?X}nbIA?A#=3h7}E6eBpjrN`ABb1n_Fd-+QcT18c%%Qrx>Q zs9@&v^RYm|>GxmS3h&XS0^f04s3{T@OZ|3Y&0`O@>XIQi1=lN@^Y&NBGNJO9sK zr0SJ^&qEsj^5mLqvxUE_EoHuR`CK^U(7Sh6&LSt@&d;gx((eZxLig^u}n^y(_6yJ!xXJwTmt-_Kj-rMJbD$p5@CDc&sDPC}hANDgDBligk4xzOZ4&*entlQ_oJ>O(J#&*4~=Og`olBSt04oQL54UHq=c_Zw^Hc^tuVtv$qZ#; zP_OkF+J)i6NbU89407I`0e@-+dw4$s{f{%~?Trj_K9GU_$qf8M8T2qC1OB88{6}TL zU!I}9FJ)-2vJCC@Tn72Sk)hnS4Eh z{b*DM`CrPwXJQ8Z|04sRCo|~b=nVB*m4Tk~#ytE_|2r=OePsrDj?Pf8KW3nxpMic? z27P{zK|j42>I?LU^;(mme{uOGFI@?MmuAqzc^S%`lmY+g4Dw%;L7p=+=;5>s^s6%P z|8M9y$9JqRuMUFY&s~@BU31N~Yb)1XQ&U?~U3<+nzH2UCRCrBkS#{a96*aYG)r$%* zSW~&KY*ER|HDxMo#FUH5Y8S4mE-PDiWkqT2YG3iXHI*f$3s;wbk#Amg$qg44E%eO= z%XNj7rDb27=L6H4vN?676_xYK)>o`5^Icr?<+2-#s>^C>%1WK=7q2b3w#?^oHp?om zU0n;p3+k$CDyz+k($cbZ05vWP%1TOa^c9ws)YMg%Eh^hU>cYypnzFfdwY8P&7L=_) z68K-duA)}U=9>>;DcG6|t1H(Qmes5lnJ=hYS$1UwMDkUyDXOe0EUB%o*g$!N<-D>r zwIx1don%CfoGMq=tt(YEWwk5TU8`7?RMpf$frVvj=d4*%xe6Q?RMwW%mX-S`=7p8j zYfEYsWkvNW5Hhn!q!i;xj?%BJtX@;Ps8Xrh6Q`)MrlPi@a-HwWigl%xH!NITS+}NC zwCr10R=cRA`r5KuHiGDRUgZtzq|!>oEbIa)cYeh>RM%L?)>dlvG?A)Gx22eMLD{O> zl6BWo>WZ4QoYDwXFCWk-RIn-(Qe9BFuDs&f3)hrfTO-9dG!$5w&J`tV>R=jZEmbSl zc0md1w^pS<`>vK*hGTE4C6xAu)3t$ZJ89y zmj*ArsH}1=GS00;Cn*)t7uJ?l`AUfZ-ZP=n3#!Xd3Yj`&WwjSom#nX-MLsf_S5{M7 zUFkCMpoLW>tI9lxgwU=kjaLAyNJ;e-Wovw;Y}L!=8NSj>Dl692&Z(^}S+&|%T3Aw3 zBbiXNu#+m5mMI~XTntipeJSZN!BV8Yz-yUdX>Gr4mGS^#~!03xCqLx@`M-d zq!mZmz@0j$Kq9x~a_JUUtgTv8roQeDDcp2EmanO-tSfs>-UXE318I@pY@JZp3hbkz&>L*Q{E7{Waw!6>EIuaQnQJ zl={}yR<5q`U0YULQd$Z=<(0njRqJZk_*TLv`PP=LU0b=n%(ohzScq!TnJUXm(R-?( zQ(t*y75q6%Evxl0%~xHvYJE8z2FqT9w7Qx#Ww6(pGH_!WE+yhk*Q(n!*I-?Fl{1p9 zEvW#vS`0vt9XV<%edsH-ECSuA6!loOq2!wKighJxDsC$C)i@O|uPuWts}#eMRjbON z0Z6`TZ58UmL5mQy10}9o2gfIgP)qp@)fKg1Q(G;hHDzVjqX-nfuJRf3jP00wORB5t_m0oA@-9Piy&fHlJ3hr;FxaJokcY&N}0) zFZsO3Gp2jb)4Zpt$;U5ps|V}#@{jjX9+dh_3YSh%yXm8y+%At)4sJddtMgFLD*tHg zyr>fVB14j^qH^;m%RbDZ*C}0Ew^Wx}!Kq~{=x~vq&u$5PR#-RpDC}G$^Cq8^yeco_ zC&J%B492*b`z5#E!xfRkeJ?n;x??M_MKapg4VZsfsPmIf8sP~Q72~+W@lywPpXB%Z z`W*OKfyelM>%fZND371JALqt#@pH)4=UeRTx4HNh2fsQEAIwv6;D$8(Q4YQ-4gVPj z-54d18Xzp3Hl8va`v-i>oH?U4N> zFR4n&1GW0plS;|f@Cfz2zo3RkXzl$?)bI$=y}vvS&$8XWd<{=sxPJv2o-(&0 zmP`w2cy|wucvHh4L4@;Hq~Sm1B5_}=;g8htD>OU|)cael;XmzReZDFUAJFg{G(7u= z``4i1b6h0uO&VTb7i!V)M``q}8lL-&?q8dR&vlWww`=&LHGGGL|BQz3)bJBEd{o09 zqv3lr{IMFoSHpi+!}n=;eM2*@;g8qoZ4G~dhEHht6E*yxhR@URzON+h|0E5ct>I7B z@IeiKiiV%4;U{VMJPm)UhR@gVpVROK8a`jc&(QFbHGD|JPtowEhCfZi7ioCsXoaLM z*6?4@=vQd?Gc^2a4S%MFuhQ@w2i(668h)ya#JxeopQYiOH2fDee2a$X`0f6+YWV3c z68AO@f3}8i*YID`@Esa{hKBFd@Xk>hNsVfFXJ`?8kA|P6@#)p@vo(C5hCf%s$2I(U z8s66M=WFH#K~L zhQCs+HT+@?f2oFFq2Vvn@T)a^k%q6*@Rw`&4H|xd zhHudD3pIR`hF_%NTQoe^cHO^L4S$7;#Jx?!U#a2SHT+@?-=X1`X!uSIzf{9VHT+c? zzDL6^)9}3-ez}J4)9_bo__&6@M#I}0euailX!sHhKd9kXYIt8w(*9Rz_-qYds^Nng zzD&bU)bQmRK2O76tKsuC{Avwfpy4Yt{0t4xy$AO%q~Wi3k+_>0evO7N((r3F{9+Bi zPQ$Oz@Rb^VwT7?K@KqZAD;j=-hOgG}4H~{i!#8R8S`FW#;p;SftA^*^x%=0q;csw} zxVLNg4H~{f!{4akJ2m`G8a}GwzpCMTH2lpPzE{K7Yxq75zfr@-HT*3a-q!F98a|=n zZ`JUF8vZs7?{nrUjJ3lWK3l_Y((pkI->BgyYWS~d_&g1NyN1u#@J$-NK*L)aeujp> zL&JwOd_=>W8vafVU!>uiHT+@?f47ETq2XIJ{Avw2c5 zH2h`_-=g8aq2XIKe5;0U)A09e_;wBdfQIkT@DFPEP7VK%hL39ahc$eUhJQrE_iFey z4d18Xzpdf__wj!<@V^>3bPc>8IN?Jxyf52~_+LFg%V#!5Yex8}Aj zbm~rA11E;?L|7t_{`e67PVFYlps2rB;4Oql5snJ{4B^p)I|P25FqeG#+XQ};u%B>? z!1ohAjBta%_Y&rC(_ba<9fY$9uMqe)!ea>+349abBM65Ct|t5`!UY0fNBBs>c>=E_ zJdSWs;H8AQMAPpRcmd%6;lxKkoO%i2AmO;ca|!1V?iKhP!s7`?1wM=LQG`1LoL5I&x8P~f)+pFr3r@Ee3rB%JsM>;D?zJi>8-y9u8}xL4pUgij_M z75Ev#rx5NC_;JEq^5}09_))^A5^fRre!`z4+#vA1gg;NXO5i&P=M!Eb@NI-A6D|_? zCc;w)hXk%Bd>Y{bfv+QcI^jHlR}%gL;h?}v33KV9-zV?_!eBMYurVcL~oXoG0)*gwG`$6!nR1>Qng9x zBithJ{e;U2Hwb($;cE$3348}(4nX}Y1ip=M1>qurZz6mh;gG=9gs&%DAn@O1d!&!iDA{-Lj?)1ev9x%!ajlDAbbnq#0S#;gc}IQ1@0z%E8$*&w-COKa8%%D z2!{!G2>dwVO@!M7ew1({;TD1KC;Tj>XTI8We}gqsNm1zt+{F2X*67ZARiaN>WY{Ry`ajte}O@I8ck1wM!H zy@aCzpGElVggXSDLij$yZ33T6cr)P^fsZBp4Z;lqk0<<1!c_tvLAaIh3V}xv{ubdP zfj>M4@co2C0>4N20m21<8ShQ~UhoO|%9Akp^tqTVPuCwrSPHO{=)i zv27A23}`#2C+0o$_N6>tSOcD^6TF`h-P&PZr}b5RzbtfKk6y&njM zLhlEbsbtWFLJrytl?VPD4V>Av9*tpi63~-FW=by8&1E{dsy{_cEQIFv`A~{KuvhNm zK7)JU%C7eVON?jB0~@2|fpt-P6rvAgeD(dn(w_G~JPJhb1(tQa7g)+tjXe(gP=QOO zBYuT!eJr;;kl%}JKjSlS#-8^AOS<0+EEYa0M^31~%{nce7ZIO~*EH5_`EOOt&c%IL z&GHdfxaF{B`5@A3MuU-7nBh|Ch>X(2N2=KjJ*%h79q;&j<-^)>hW(FIfH%B6Xk-!!rX-VN;GZDrtxG1LIEWp(m zQbc`@|BQs@d46^t$x1-xQec?b?+Y}(3^`rVpwFOxj<8zn^|NVCsyIFLz@_N)Ic4oH z+oMs-iSU^AuQ14~o-qZgS{j9e$sKx|-8~;}^y=5h^#i~vZ3*1qVxyft)fJ_{6<`KQ zdxdmy50R(=IKT0JU`j8OUX-LI-E4O^^9YU;^ixwiB*npw5 ziT1q7KH~eZv7BcdLwWGl`Ui*5nt@FWZ6n5m$e)Eoc&Hn|y9s>_|1ZfiEptL-LB3UV zoTB*qpJx7)|fok2)dD$t$WeGxHaovIZF4MXo4?5kQm>)*p+>1uDlZAGfV%eV)SBBKY*;4T2vfJ#<% zD?;%>Gao}QL|WHvPeMGN6wY{w^xK*Fa>=~RXm#>UV7}wLeEU)=MVFmp!1gv4gG12R zj!g98g}s6NJ{AxD+qo9fdXlFtLo)I)L>yBF8()vB^E)GWDADIkZcdM<&BaF zP-u~YP-QFfU`QS!KIL_ycJ)1~Wh|qOCGg^}QYFAGZR}xt12=b!n<1QFKV?%EH(7JX zxNM>Q#l|LArevFaIoU$ujma!j;JVz$oa`pOu_VEJC~w>zF&+~GS@0)D|2alO#Ap}9 z93TSuag5bv4M-^S5?YY}(Oz+LHmRJAHzSO8GmnwXHZng+G|2HBEN%ej9}D+gn8Il- z(ZQlSAsrrqg`K$Me(NovK&$yjYBkz8n;LD1c458DND$9vw$s|(_J5_6F&af&;I$8$ zQ=H$pb5M*MT`BFLt<}S@R(t~UJT{7DJV7ye@jw1cq+G{8fyWWkw%iTsu*|OXRomv zH>A>_$vqmk)upMxz`5r%&QJ{fzwBer6>vS3>F)sodUFZnj#2qJpcTRQHG+tgEzLV- zpIB4lBruLhEEFeHLO_gO%Q4Gj`1Bl*vBr6zf$zqX={?PN zp1ds2A&Uf(P2u{cthy6awhdl3mJ(}ZHsU}t%B4{$sjMBE(ZgvFuW5X|;^$)4%dvF3 zhxsD}QpU$RqEg>dh$zR=-JVtD-Uc#GgJdi<`l#AC)dlU1d-&&2ioXa~QCa`3DYSb@ zyDuX8X)>Y_QBRZ66_IK-8QnXKS6qWMn~RF*;lMwLMTiKeR0wM$2Bu$cI!=!9Y+}35 zXBj&K&l|4?p6|i!r-+Ji`$^#W1a3%xIRy*jWV0tiaf8RthJ0R79JR~PuwiM*tngOW z{rtdPCu4g@YwVK|LVAk2;(MyG4+Ao3%*oi`C%aQ(gT^ATM6vFD+>J8t@9^?$f#9iZtBKLso~96KY)?vM`z+Zq z$`GkBTmTU)e94JSqV|H+W{;A*2PFPme#Xi!bF)FbU}1-s?X#)bb}}0VxQ)eN5Hv8p z$aJ!Bw-?a-^p=#`DpQO#E`q*QA6bltBC-Ev&Xdx`EE!AKS4gWUNUQn^X_?>CTibua zKoVK-k4_HNOPNDGF$bUAZt%@F*bOemRl31D8&l+Ty8+8?GVY8tl2Mb<9BCwn5hyG zjyngbT*5$ynPdHJj+J;h8`ay!3uvaFD498>jN09wPU}5^z+JlzVBBs!l8(pom#=Zc z=vwbl|Dn3jQ$UW?h3?pu+J#m?F(|mvxsN*=>0TF_3;?zwiCzg+XtPvX&nHH=_1{1U zAku7odVNY`JxD8j%wu~4^Q5@VGZmL)&$O`wIh^g=H>a>wJ=3icOgpVF1X4{#nKsD% zvwKw+q9%|H;spyI^SV%9s_@FvbT?`!H|lkvNUB(>XD3_48T2uPqyOeKGrZ6e$DnWn zBmdu!K8WOV$*Yl`^W?-7+zPPF`R;@i+(e)r;_(C32&(S} z?u!PV@AI1FVYDsQsHqJ4B}~WbpN%}n@G&CsBu{ZfMEuK48Jj%1l|Z#xjisUdI4Eg98uwyryZyG) zf$dlEfW-%`edk-hfirpqIf@Y1I3}vr@p>I7QZ*yeoU{m#@34M~3^zT6g$uK}EP<(s zvkP$Rz95>Y)QpTdHD7dZ3{H*0w?|=#61=$2jKSPzMrtUEv7c!%m6ECpCZ)6 zjBMt}hvoEM*>H-82M3m_05;A|9Vm_(0GNn1o{lGe4J@I@LB(gc1 zx1@MM1tuWMd`A>^a7~nl_A-M^1W}zCpno|?HK4px(MOPRf|K!x)QqK)5nFH1F_YPh znak}JD&yagG;p2$G-|`a#%i1fipXYONeA=pCjZYt3&XKj?8Er{GO~)p*)qUV;h=om zZpX#`Z_%sK!2Amnplk!e{yQoqK~UWt_BIr5zYRnu=+WJZ2TRS2O?Nvo;-3Q&*-SPg{^@ugI2_r# zkliUg=Kn0P{B!bH^#>ra)-eSp;0=2=}$g$Jy^H5Cq8e`zY*-&CBq1NQZN@nF+# zT=#dq6Fe23XJ{;Dmc@y#cP0WF@{R39-P`-Tx`X8km_j?+l2g0FiDI4hF^CW}6Yr!E zM?rHOX(lO}zoK7gG}}qD6EyjxDNr;Fsx=zK^Eon|HKs+1XMh&v7IvCecBC+Bnb{6v zJJ!&Tj1=y~O1gtVuP`S@3i~WGj~H+XP2z)9l+T-LMlRp4W&wz%a=0RyQ_RqCc?T|K zSoyr6O z_AZznVyA)@V4aR;Ka{jma1F7LeYh0bxD@ZjWjRXEDcgg~T17}jgY%1=# z1Dkcv7x#*->`bwho}{g?B4n?e7t4@cWCrALYDiUcFq9_i9rSqEEVg2S{NKtRp0itE z)7(`f{r^Yk8fZT$bOCFeuIW_$?xBABsNda9#l3eJd!XMw(eLgQ{q}wW{c;sa)2ge_ z)c#A+*x=j-$>H*P&T^P)PT5Xe*7o31x816bT6HfexA8hf>})E2cAnMURNP^G*i`() z9o#`KepaOINRjr5RB6drSp&H%YhV##4I+gMg=&cn`ocyHQxlQ8mtg&Xi??HHx15Gz z9GH(0hZd8tx~1wf9Gc@`iGMmwCeU_Ft7^u&9ipQvtExB1|!82 zLrrzI^%1Unn+oGi#e44HrgHIKsY*PhDtnSuiC|R@LrUF#Gg3b|$BNq1>190=MF7B| z$12`$6(-UO+b4zDO@(`#isN?}`%suIh3!o#3`M&hH&U2DBSz{5BlQPZ*36Xd@PD_g zpQMxpKOa@|ZyHi6S)?8XyGnYhDF_aE-v1u-Q?=Y}VS-elGZKqTlO7N;( zFLj1L=tg80vtGBOZ~T9!MRN$7Xpco%GH4%=jO5Id`pmJtru7OV zX~c?o)>{fTEwy!;95fWyg(&>r$<A%P`+KfAuZEe zy@}dmQu@m9Tsg}V^rpNNYWb4~XQFl&)=4l zps#i=iLu5p5iNE$w80<oC*00tqO=x-e)D?35^of#2Yc&_wsZ z1~L_ZFNCV=9bO|2o9G;J@^dM=7ew~zgN;2IR&A#~IB2PA(}&tH;!LT{8srA9&#R4; znpPs#uJhGm`*_lF?I4hYIqsXG;J`Ao8HZC@RjKB0KmgShqsFc_^F*802X>IU4{y)= z=cRoa-YZ2~=+M=D2np2v1n7R62$V_pP(3w2^~bvAC%BrYJITik*{ZX(5rU(kj1YS` zLiAyHn6&AQ+AHob_Tn)vBL&7^ZA#LojTCiu1T$_=NAJKYprhr`(GjM#G&@q5XI&Ds zy6w9iTWCo-H~UwwVv=+;TFL2pv=^uAQPRawKs$`2O>Z)LDz<5j4m3lXVB~El!Fm)RI*=Qz)4qymmF+N_eB5ol6HLblz_g^G6eb&nG6U+L<Q){?bXF-=w~aj(iK$D& z;q47ntFBS34WnpI`Ev})WK7@_1yd7OO2^55j%?N#d3uO$@o5I1q_o+5+75S!*HP*5=?bCIV)QZB6-LDsda1CF;?`s;Ix7c-5A2I} zD3!vs^rDQq-MFmn!-W&yAg91VPKJY=79(<^N}61)Rk@m7?P_*~tJ%daO%XNAW-vLr z@gZ~^9#RH|h42_mR0BiAzY4#s(ip7^*h?W?%wGhMORA15iuf;(^xA-Z2RcE_UjUGQ zZj|sCnofBzC>i&?4ds^=`!;;HayBORz zlK}T-l_BWt-fUh0wAn0s5orFmhM7SS>NJ}-SCK5zNGY7r@DDxEGpnT!X)B=uucb%Q zJtX)z!+Yu#jR$p}Zo{bl@;)#s=4Cjy;dPj;OQkH6uhxhS9_1RcQ>|#1Za#`Q)mS&8 zQ)e*r?+h;}HSarAno>}5FoXB1Yk^IrcwdGeRvcpvR15++MhT*reGC1XB`d8>gew)? zgX%+G>Vv_*SD=0an)! zkpPqHF)>{XJl5^U%+YU7?lF%M@GyXfePYsz=pdMGk?o=IyII(~_#vO=teW}3`{wo0 zpg9>sAskfnaI?!Et+qDgIDMblY;K5PY>(76pI?0Q5zo47jF3*PRpY;Xqx(u!~ zW;}r;U;mfzjE3!t7*C*ns&MX?s2=E#U^5j~l!1hu(ZBkjBn2cXJSfQwl1#}=67wAg zNl8>TntL}R2^*r2B)KFbiAfTtn!TbIOfJkU?5)vcWW5}BXiMH(djr;urjDby9t4$9 zP{p034_?L&x$#66c+E1N4cxgEdxWqSP^5$p4d|U|p~JoqI%yv#Suo1f_YTQLmj11p(+JXOpeRfx zy&-19kG!6jVemXG+4G@OapAn=__yMxGMynfHQ|X&2@fO_RA6|}aibZsLkokbI?DU^ zj2l?@Pam!3TTr%)SZh#{j94`<{rgI$^yps`wHoy3BbnMn>Y)asxDTP8<~WJ_=6UM=P)OasJ6qkKoT2W|O~c*lk%iw^n>H5!YEql=i6%MwYxOkALFu&0 zZR5!c`~G)Wwi-^IQM&F3S-0yyhr873>>OQOVjC#kqXVPdAs8JRapW;B2)b{9qhi2c zXTj2`4iRKY3joW1D0zhsEA8^3BJhmZdFgkl z9P+{BPL75QWAYm?@x-5tXj;`df>$X}fzvz~4k~6Zf;)FH0C)VxtrCC#(@ls3y(chH z%Qv6eA|m0a?$XVUs3Nu_DrOc-<%Ax@=6gp^vh67LyXHY6pQA-|M_wFeP%)4y@ZD{m zq?kPjZ`e%86^%H@UN`|09>h7e6T*x^aDnHQ;S^!!;37_kY)wVSClUFAI3bCcB#09o zL@bmCi%O~|^~}Pq41RH*bXV03wGf-$hw6;kk8p?(ST^Vb?lY2}MK2N%&y(Z2Kh2}Z zg$B6olT(<$g^LVyz^0oYXAWjV|7@2+tW)#k%u*wP2fo|7d_SAxEr)iP75%DHFRUQl zvnj8(3b}0#btvgd_ZgC&@e-u|x4YN>%Qc<}<4XpapRmTWU--Azn9E2e^xmnvGsdXl z`cAy>hR;#J0vT~o{eexV6K=+0M>FD#=DO{YhPF5dK?R?&*aNagzmp9sM#n`8pKaoM zS=eL@9pCgL=jjA2sdvSL)@!%Izww}gC6NtiPL!01#LJNd!K0I{K-`;&bj;|c-xWq$!H#J1uxDE|lQ1WQxQ>XRc9)+_QbQ z!~3A6aiKxIzsA)Do)SPI(PVAS{UFpvxmo^+xqVw+GNEcmP%D0aUODaLt44 z{1@TG7dhs+h;tmoB%M582TpfMk8(&0T*P4xVw#Ki>tl-h3>RTLh}lHMLLpd^)YF@N z!kG;V0&9Pj>{YJA5;qmt)S~jki3Mfxj1QsNcn^s#P2P0{Mm)T1Xj5-Oa%4IQr4TM- zToRaH8W%T7!gFCWVHi#JK$a79(%qqKIZ?qEK5~lLsytSaQ^rPI>nucEK~=DwF*N6J zj8PDC$kLRgF{{@`$|9tJm`^N1T8I$(L{8}Rq!i(O^m$4W<#BIxir{N%*xk4H>>V0{ z^Zm4oo+fAqk~bJBLTDoEdVm)QDyk*Z5JPpQ%ZVN#%o~|-0%Y=<`Qv0V4^5^?BNsJX zCM;x*B)le*XLg+C3`RmBsN_UGvhK8BJUE5~yM@{Lk~(Hy=c^z1PKmQxcb1cxmX$zW z*TUScoJ7zn+1&1Qg&!ZYH)dPwOf)*-D5JHVI3m*D` z(7C55)y{Xg{(w&%;q-UTEWoL8f5H25@)V3OMFyvTMEu79T@hV->5e&m((!!XEtY>Y ziT(e0&pU33)pWI62c^w2?2Y4bN63WMvG8IWPJtLUo=-FOg`@bK#R2;f@R7Bf59)AM za$i=j(usXL@KzKdz=luBpkfB5Lxk=9Q)%X*VPOtOwRSV{(Suz6h(OaAs*Y=4so2>G}K2^0sSzoi2eRDB&FqdQdS02GI__4TYw)q$8Yh5 z=g2AlgOqduIIyIO+4I@qRE{udNf5@ZCeEOsmZ(Yn@p|baQG7p7r;vV)+&tf7=fmYC zMNdh3=*f&p?|62IhDKbK;wa>BOpkCs_GyoWsNR2ZD{Nz?F<6aA)<4eyY7XyY*f`)9 zBXJy%qMxJF&;EG&9Fm}yI{me|Wr$Oo#;6%EyK#1DYzp%uJ3-9v!dSUkd<;jAFq@D3 zk$9=*+=stORy39|IqvAoji;C~NQ!|PNt8SjoIL*PB#(3azYtaY-V5Z*b$$QCe2n*_U#UVG;xw7;lD+K^(gAU&;K`V1wtW^B51pEaH#|_n7@d zcpgMM=K+or{;Gn{aMOQG&m>cEzmElgT@Ahml{@eaiij#__3rSc2Y1XI@nKYf4*~7d ziZ($H78&;~fpQ<^E$cONW_Qi0kagBRaR@tGans+Rv7dq8vI`%F6cK+p^N28vLkc-$ zL+3K)UiE!aPUm%n$t2+WGhz_XB^EMJ`gm13R6Gj(pq2gLvS&d#(`c=J5e(bN zupb|aLgw64g@@7RRxGgTn=0YRWWo+o`hWX%mWG2xUGIz)b<0*r+~iOD(I(qL6C=$m zqrC!uCYjHf@t>i}>Xb4%QARKF^@=Ga%Yfl=pC)FHRb{C3EUp0g|9Y=*?5y4f zpqGGbSNeZ0kN%f=lkXk)Uy(=uc9rm?f*uyg=p4uc70d6zy+<>;9+5?rbt8#q8lBZY zYe^1wd759s94a~yOcG#%EnjRdhT~{XaV4{*Ks<^o`J6zx=kumqiD^HBzKWKeB}x9% zd7F%YY!`565@4)|SFk4cnp=LdQ~h}%Yxi^l%T!X;?T)$hE3LE68hk zEF=W-bQnoNlk8xiM}M`7K%3i47OaJI&|4Jbx}%u?HXwYyJlMJaZh7>vd8m?_V4qRgSqi(%!9J+4=PK-{1?vweD5#(u1s$uP&noCR-n#6uDCeYPx;OJG z`>u%aQ46SI{?Y$MaS?xb!%0D!*GRx?{0aSanN(VNjX$}at9BN=#-EUq;x$CWvhK`v zDYP@@S%cz-Ig=NdyRL9Qe^L&F9N^>VK>k$yP0fbjw%p|-zV$W;KX@1dPE2C3Z^0E4 z!SL&T(wOj#hq)zu0sTl{q6orKBv{pfOITW(=Xv^WwkkXiJq|=nmA(R>qs1uVNq;r_ zXxK=wX@B%Ia?Ugo)!*~vj`*9%PW*zuUOgrTI36d2d0ItkCyLC<#IpJ9bS=6S_ZdCM ze-2r(sOMVr_QL#9l@ag9`=vC_xGI}YIjOI}re>82w%8*<=R?cB4SlRZ4zQQ;;BBvE zzl@fB8!5Iv9*Fp&o6{*9Tek3jL^sC;UMM6vx{8d0$o8rlDi-x{sCZkoG)bJ6e!Kd1 zvUlnlNBx%GCH0F3ZkquGd+OvVE4S@1&l}cu?>8e`dfS~XvOvBk|0?o*ZiI|myo?yW zBMY)kI0L6u|NNw*>bo^D3LxWC%%kRP-%*e8{@avZ`+#SYrD(mD{e~I`cWPNG3}cl@ z2j(D=&Yb_CgG;rJaQve*qt^UUQfZz7`ykvglxU{W7r1S$j3P7y{}>=0uWTm{Ao5xa zcWOV15yO6wPbX5>PWO-a|Km>9FJ`~|9g5`tPX~NjfgKLmroa~+@E(9Ji(B`#P#xCR z4d81%SBq=fuOEp;kCA$XsElD@SBgi z+naO_z&1gsmVPXUfYBylgu^ZoIn&vqPI9zFLSy9Z*2gId&OIV50k;Sa6>eCb%mf6= z-!unv3+iLn7BYJog9|=WU=KaUtYjSI4QE8?1eirQSG7^f8@WIT9Oxf`*JijNw4r>; z2p`pg4_96RRY@QLs1E0-RJDe_?Nn=kF1QtASr7%Yete$jJ*O>c#~p?c zili~vNCwJ$QdfODAmRoZL&BuaIpS8xjD_5?oXfrE$( z`{7cf_sN0r$fos*;mw7%3)>JhbrFLfAZ#T|b#7E|A@13FhcKk!VS>&f2ag#-lq?3e zSQ+6=kJWC1BxukEhnS}@j0TKaxKi7r@$Cw_T~=h^dXFRC$X1^rBp%A**(GGxx` ziEL(uaCa7md3WU@Qrv*x8&)?;Wuzdj-jW3hLw;3WSQuwH{98@uj7($!D)k1xH?Kao zK{)&_G&Fv|FFhY?1cAG+Ldl+x%Kot*F`^o*t^xmxj`1~YIZM5u>F6)6=PJ( zYFnZpw1M3hNE58l- z5I{l9LzI)+X;a#H9|H>pQ_KtwNdVE#A<;%u<64I*lsj5qbx7Q0ICn*^%4lP>(`H0n z)(*TXBm;h{8!@!9TQ*buE}Mg?-mMMHLN(m`-(bUad6_fIkcEy5A2GShv|clDR2?B; z)966sTZgKch&u4|IOx&dzJIa3NBjSY-thH9l@%7s?lVxvN*-2swWpqg>z3UHTlI%- zfdgY&5I+A)@^MvsV zb1))MF#IH&DC=Kp>Pa85JQZI&a35wM)3N+v|K;n%x}ScLm_Bvtl$V?zdGA<7}z16a=xxWloM*;|e!#tTCE= z>`6ZMx{uAd75HMO+1%Iw%!^eh@MXvh`7pnY$TEntV^)J=BT~>7?tRSc4P4f3H8vra zBMcYy@BvvOS~+*HgypSUDI0U#a-@<%=vBoxs0#YQH-D+&bEwY&uj%4aE z_mj;)3%STJx!c;jnl$09qO7{};L{1qVB2tyuwsgX#?+>{SxUes{Ya-));@DL`skfh zx{Geyz)YLj7%D5OBRo&nc{14q&qy=Lz05M@c;zlPD=6@efROgMG;m>VRS_w?Zf^#) zn+C;~x44 z8r9Tz8=jOnYLE+ zp-M|es!}eb3J;F1r3?4`D7BT7XPCAW69-GH~?ndNL_P2#D#OYXP z+$j!(T6PPJcm-aH0`a{~FiH0(%F9U4Qu#b*63gH!oU*-@-s$jMcxMN^@yZUaqiV;i zhkw+LRSyU1mTDa)7lJx^lHeKf;mJCT26{syajZqF*0V@edb|OFTAGVur_s4#ytx?P znI%@7$}h;;Ybfr~JE`<7%9a?>JFZLltX1f@{f|=B8inQfE&5=^!u4CVBNQQ?I<*ML zlc^$nC}OxECWLc+e3}<~-Ab(|wDuqx!jVLBB|X_xxGdEanDXwYE|UK1YbY7@a&6{H zVZuT^C1DfK?PnYp(TPTI6}Ny*auwA%^1{X==@Eyl`kja< zq9eQ{Dcj)&VvRo9{x)9hD_C*MunjgC}&C^DKadMFd!v3yN7$xZgVWSW4gZAM) z;+t=0-n7-pS`_Bht_aa(537giVJ8kR%QtiblZ`n3W6zUsCG&6#UO}|;lIxXV*(t-i zI%R+@q>h?wkYS+5Sp~`U>13`@wsTesrx-e_d4i^=|Ci8}BIF9T!ybm66?JLJ^K*&> zpDjosVJSGD>a3j&@Ga?%(Kloh!GHqPIKlNaBx%Nyl=o(}6ykDMF~npv0+{t-7q3rI z)}4vmfKl5R+mp;3(AyDl;fY>)b5R_09o$18y{Fxvw_Hz#Xsp(zWV9+r*0?;luM-0*7fGQ>w_FUHvWm=88H)A9WTJc;*CWM=UYDjINrMAFE*8xuJBQgfaJQX> z0XG zv@J<#`#{zzWXpvN{gdC4p(v3t{JL47={!AwNcGk_s$QhbQ*F$~o!BIdU2c%HfpI%y zKaodi35UJj#a9hnP1#6l3&#%!b;rP$0RU0l;gnm)BPR|5Z-H1xqE&R^CJ;tC*3QFS z*`_t2r~ex)Eo^j9#T|#Qe+mQ+du%YNt$_hE$yKUXhvlkIR?NUzstk3IEK$v3+RZFf zIE)GdpiN|pt2xPN>;DIaW9%05;9R)A_3*&gS~;Z%8!c>7)n=ep&Ds*UZ6Q;^!|{5q z15>9;1_ve~ZP4tB;(SpT9`WKgS8fE?Ht*G2>umUn?BZcY^xvc<<^TpSe9 z=mEP52Tz2?(bsyD8x*on;iTf&9m(Q{p?WZ6KKC50*dZ5LXZ3nM6h>Fq0a@CSfM;~d z&bqRis*{(*;!Iuwyx~j8`n4(+w8pa{>axJdf@(u zgJIjzfB5?Il_BESA*8bW7$3II0?&^RJpV@5JK1B|QGtq%QUrKs(nF1QQ4`0?gj1?R z+A%52{KJ_Y=H^T?JKRMnX85S2Io8q7RE+b^&!7(QWn5?o)$TIl>QL)cAi6j@V%HQY zHh=^>tPQ0m9C9M#9LI?yx$TmZQG!mTPsqbYFKr5+miPv6gTP@dHF{urMwE7lAsTm# z{DJTeWmO$$%Nb}0wp|BI0ThFWWAjb=4W4cvPYs6$4-0If2a~7UKT=Qrz@|Igr`z9E zPh$d`WOk20D{%Xp3O72i>2CMw_HOkwDzNEZ_v!Yh_{1sqV+xg}n5f>X8qm=>q%CkX z%cer(NV|8%^EIs7fXP-MZL+;F6HtvqbkZ_X(6AwL?uut8(N1B=+u~^V|82|OS|>ja z`~S$g;^>Sv{wKVXim;>P2T!1(KG+pw++M7jw6SGj;*Jh^fMr-m1xjlU>8L~{8xHR| z%O}Ztrt+Mx@Zc zK+L=zC#)L_lu!9rMdf@`x)tl-nxv|Cnol7?nol7rqfhBUzjl0YJU7LCD}OxEORtEI zWo48cbk(1Bk{lM99KGUmoX4jteqz$!rwtjP9pJCG_h8`V0E||bRk^{xY15zr3DmWB zIOoIn&$9}5hwFFyHl9t(U}zOop;ey>N(b3Ea-_mm$9n8<)pL2Dl_1%fCsd6u2D^z+P3g|FDKr^Tio>3~Kq@+$g0NdgE z`+OVmISrH7~)GQyl`M7ihaV4Ps|VykOj z#6XSfTVZ{WlA#7d4u%_e1kzHWY>E6%!n-(J*7mx_3fNYUzG?y6YKLt- zhN{Y147yB@udV{+N97cdIz#0C1d8v^($??@j3?|o)rr+3Pd4#@6e2aGdXv6wb)wRC ztO`)&NM~+@uf6?0Q2jFM*)(b~qD@Obu47o`vKd$p^LPyA0kgxbfU7DIEXAPUq+vX% z>=_=nKH3jZ;CVp=qUuBWk1wHkJk^ z;+{Y)Qx41hZoSs9LCAvcP{-QhGc?C1xg``g<#QR0)m14P9$`&*x@Wq(#uKSa=#3w< zv;QbEQa8|u+wSVoYj+4}40`Pzt6^iK0B1U(Opqt_+S%;t)UvykphLY8{g>$+Ce$edRnoe@NNcwa;N3zuU0TK?lkwu$IPZ@FS|M+n$cuS5gn(bG zZ&l+)E5{8M5K?y;H(H%><05G>^k^ZGuGpSBZp4w)uEved{zB!)fn#4E!*QcE(D*au zLKIdXJCq4U;Oruf=+5~_XFUs@ENvV)jpL)Z%bV&$?0S#hP-w)U3BzpqcJ<#>-zBpMxV#i)3@5- zO)W{r>QAokAJTjrec$u(`gT`7y{_3m7P|r|5<*cEN33xB!0LZmON==1iHN{$e^!cW zgF{18z~oU>leYZR-hY-vB4~{bJm%hkzR~W`1$SBrx6b_C-*#?3b zoaWH%1fCKh8+}5Q%>w$U%IDF;foSx}Ed!iy{k)r`R&kTNA9Nhk{=n|5vO25Eo^DZS zZ+cNQTNwaZ=@saP8G9>NtKuL)PoL0(XJuRL29VvP9uTo&Se11?O?IyF@IX@$Td|2j zIVS4dVZIudz}>$<{qbV3dd)OEI4aPHg=A>N=|Eua_PplfMxo|vNQv3~>`)f=z_p?f ziARn?b=(*PFR&DuJcB_0MLCnK&_)g`o=3FpgK&eHjUWx@I?nipQkArFsAmB{NH%I< zs*6r3nUfv$y<$y(*X^g)a*@$3N4Xjn;T?)fe!2!Vu}2sy&-oo&UIGdmBnfYbJZg>9 z9x(*kI_851P>c8SgTeOR-%GYZW`VlU!kiw>4MvQ;GUyD-po958N`-@R2mJ894Tw$J z5u3CjPJ&Gowjk(iLeRMhow6P8BPoY7TXp9)FCuAIUJg~GvxY>Rx^ZREh27Il@OXHo;>c7YWZ=Qz+ivcfOhe7(9yI&Tl02Mvqt+40r0yyq%7ItV(0 zKF2{QCa56WKr0bj25A^}Bw)!OKlqwe$FDHqrzT`Vdjg(qPGg~Yc0bW5O)h4eup&vt zW@lj27%&CYlR}Fv(_#5#2CmrQ#LfHboI;H>!X`E9sq%uU4U*m%hpA{II-t(oC8hP4 zG|wKTSl~Ug2u_IW?TN8?m&rqlm`5kXKBJK36SZRVC1^z)wo}f^X}%!1s7H0AQ8ASW zj?DA;=>^uBKsFXnv)6yggLZ)z)!l10FQ(z3QN?w9=eHE_0 z9X91_5XURiaNo36pVoz*WquW9=2=OG5E*7eNoT?A!nuwi!2q$U0%8sX_`l1R^C{TK z_}M!q7U+n-wI1`GfbWL!WWfxo3-mNOTAqo$^l(JkKce1fHj7SNCvvNLG&7)-?Ov25 zXTVC72ZKbM>C~R2r1PnHDk@IhOQO7J7aTYlFlJsT#OVMEFI3(E6;Pn`eswCIW0w-8 zOo?JEmgEd=I|b03+{ZZjG~`s}HoK3|*l92WneYy(R0n0zcA#%kvT}t{z=KZI+@M-J z**JM{t`q_PeQaP^-bpBce5JLQR)~#&3G0ICa2>xrYNu|`=3*_uIS|S(&FsU)`whi| z{pKM!;R98}f5;Vv!>^o1@$>Zf6}#60`LD3q!LyykOCb7)KrZ<}-(r03W@Aqv|Htyxn+5QBP6=IL4I%J-oMF;~K&B*M=+QY0pK@wZW2X|}@!{}a-5LF(c?j%L&s3~ecV2|s&? z((xk|_y$;^04RBhv4^FK0xtgqMGkL@1e*dsgz=_)WKq={IH}(flo^y?;^IdOm+kM%Fl~o+ zt{T8b3O=lK6O8~Quxu3Pw=c&#Kxg93{WAdNz6}UpV0$&f zdcRtJK+Mtm)v6LG`D%W=*U|egv9dIbM`pttUI&z>Y?=)!iWQSjQ8ly-k%npKe1x@H zeaATUU4I7Zi4Q^t$GuyGvvYnMSJCDdMw0at*0&$8T^wqC@m)P>CR_^lVlPL6rUUqZ z-{@QL?NF%^KF~OD1fNk~JSWox9#m!{4(F`d@W`4rT?- zVuLh=NL#{=s(#W(@c5~8#~QX?!>{z=$Bxy{{2;?(BboY$m--S`F0dMi7dqu@&{H5) zun+|)&gjwnM)VXiS;ij+!tVmPsFyqxZH00zQ{VU9Ne~4tQ=i@KCAbsj-y8TQcFrNu zZXJW4V-qBQj1AwVo>Hh%ksWmtQ5H)j;b2sHA9I6gT6$cV;*$F?t&jd`*=xk1GD_E! zUM!jtB2xet%I5A9O0@{Z)}KRK0iua~q8!;RF1CS&Yp zg#u@GRhJ3`l}iCC&4upcScz9-!XT;czFdTsj z!bnHDW0TV1JC3YGb`nE&n6W@4SKdYN37))1DW zPRV^(zJo5&@c>LAze1FtdJu~e&qKU68v4H?C8{3JEb?hjDSE&2TFfkC z_szF41w5|yeoP*7dNKk|VzQvbO#airO~NK=I2IrDe@2x6{cY@#&kh9gUz33#&R{_X zN;Z*V@F!Xc<1UTCTfhM^oWUbtfSlvDDgyBtV5Mp-JbCdy>g4D1{qnrnX&Em5b3%*7 ze;d5?of5pJACsSv;G%=6etSTc1zrp=GPi#5zWM!#5Copan>wds&{rU)(&_=nJp)g7 zXCbjb5=nwr0pyt&OFnuM*(~_ptY}s*Uj9Pg{qm|%fZB0J(f=B16Kmwgmwb2yTu9O{4 z5gfZ0oOCnkpv&A#(M@0yaI|#CIgO>jyYwtDo&JP0dM<~7kE~_kLf-sGYPUWb&;0}wT_T` z@d)WD47e$T31oX@Gv)Le6FW`(9t8`>OF1JDuoWF5mXV1Fx>uYiD2T$M4y&6-M8%Ujyj9w2V_LCv`<^eQ zRt~D_NuQ`#Qq$$Jf#7xO}WzR?jkOJzQcw0 zz?uF721NPVJ?H}Hlo-=ejL$Pu=NEsjc7HW$B;pjicIxpl8FcrrYM0C~P?=F6<>om8 z4U;%U2>;ML3HC-NbPIR6g{4EoPtb^5C;zv2F)VCivjln?gP96@MPt@pNv;G)r85B#Y=Od}{@fDsPgd9|3JWb@chT~nqOg+`7P{bjGXCQg_5_86MtJzf{~3jysIa}j z(}-DeQWM4S^OdHm*`(s}$C0w{aB;d!53rP^~v^l>)1{&v?USPZl03i~C6byZ%Vuu~P*RrwT!JxyU$!EdSsb#<)$A=6_Odxli$CVbkOCY%}7Y2p4BIpZk~dalQha%)rtOW(qKPPOZQbVXzDJq5kpBdWWUxkz`oZvLm z`rW+CnK9Yn!uw2Z6|Dn9Ki59|ry@I+X4vKUhCkj%x{6f_YgxCipz#iIYuuxd_0 z@C{*{RFVx||JzGPHjW>t&j9ilsK)txLq7fj6&`#;)+>&SHn?yVrAS<%l7~2aAVuSE z$7o%A`eZ5O+$p?S=%5>51Jfrdetqr?99h$D-U^0gI~a*@!j7l{Ncw1Q)ZX zV^XxkKLrqf4JfUXU`MNy4U9Kw`5)`0Z(`T2IrpL2?4kbu;7#p?c3+B68CwIrIDCl> zf*x$rv*SEUB{kdolUgNdHfK&UM-p5|HH;9JmI_3rPUoD>(sO%2p{|x;VYnneo z|Ek-BOFgKJ_V?#u#i;`%Z*rYEr?`##MNRC3Eqdg@Pmijf#%n>#Qd~+D_&@D^dw5hu z()S?{KqV$9D!8bF0-|yk5tU0qG9gGJ5ds7ihe>8aB9lyNHEL9rMHdxZ#2~nfH^j^Ce7~x$K67T66Zd_e?|HsIK1a##oT}>T?&|95 z%el?J17~n_=s^mauF^JpNBT4z?-t7!>YzC#Y4A0AQml5ZA0cA52$8zEa|IDQM2P!{ z$i>-DM|Gr};-7N~9W~=9aUi7W5R&8#Ou?Q-v zN&G$!F|huuV+4H3e5WeIjj+Pb#1yC|rC)))X&Y){D*YXlg0U3gXp_;_!bkvvZK1Z7 zYZKpGL^Y^72lEabp`g^ByAof(2~|R#MMub93ChrL^8MS)ho}%(Z{Hi!XC%t)rwI2I zy+n7H^FTW6fYKx0pzqgTo485sfb#7P<1(=fPt1UBR5OulTd_ZFV+xXtu4z*D_nn0k z6zu7AzD+FpQ%-+IlbO5ZsWm^=RG?S!jG!Z5kDq{!*>Oq(;^|)&P1v`{xub>h{tM;B z6I@P+9YAqwoS#v@!{nl)^AH_1(b1Q5L`t3iq$5Otj?Qn8XvZSw@!G`S-^mpjI-Z*C zq>7xQD)Pz5I2AdJhA}JRs5wzn(Y(lcG^Vt>oZmnNy?pODy@u}_Je}n{y2$x0rfKx$ zrNmzra`v0YarWQD*b#m}c67zd{+3Ag_`Y(YVZ`LTb3xCaus@T9_fHYxhr=p4#6~xc zsLg5s|Jj&dDCpsDrA6?+)q7e!B8;gZKl7*oC})oNrOY+uE^7p z=Z{5!o9F8D!>Kq`#lS_s1*CG(f{XFDdNch^sOY1VB+f?kV0Ko`f>dxYDJQ`FIIR{X zezE|Y*k?hLO4TNL!JFvWb2Hb<)zbLo2Ccd0YFBqac@m3f-lB7gm&c&VQn%X}?rO!2sES{;? zvKK_CgCbPigQaEhV4s%76Ly+9L=MrqSMyt`&A2=d-YiLQ<1n5^;3O=kf+9bMFbq-{ zIaJ6y?=zlNXemUhTxVki2+m zlvhthvRw-X;V*l2-&#J-!driECmZd6(#}4#`$i zGE0X#It)cjg%}178pp5-u{?No=8+Cd4qpiFlEYo`xA|u5N2{S2)1K$&=x=P|O;PmK z&Z_SCn>^=yOd`#g>>GoPn>O^oW;=dyzdUw^8-2{lQhOvv5{!ClFs;Hzv!WFAvsGZu z2_g#oO2Jvy5`1$lmq0R(ljv%d=p1XJ?MMWRw@EqDhURT?QniVb>h51n^$t7&HH^w@ z%CDxPSBu1^y6jg|(ZhzZseT;&YxU|KC)KWBO?4b$Ha4&Oe>D}oJ1;g>;jgB;OQky7 z+Fk>YitgjFZ{@r=ss5dXR4=!b>ijsV-u%^6&A)KHqDpxGucrEQoK)^#O?6wGRDFIm zRc4%2KaTn}Yow@D=UFVD+aE#YPNY;*wo~y6`ufBzm+5a~>c}tvkzvG~%P3*op)@g@ zIw{@T_g{pGCsfzF$X44ch#6?t1bc6wt!a3ky^2|+Vtq?0&igeLSN&=!K2m+HyKO~& zl1`pr8-^bEH5IS?s*1c{Q*m276}5@i=e99a5J#j;9A%!t0W!@K`efn|S`F7!?8eP( z)cROJnzO$8D>3~1h`CSm2iMRB#K9wY4Yzt?;7@eAJyfb>qGKIG`}Y%wJ`AZ}>a?LV#(3Jy?ty7z7#rCR z>yCEKr_F;QR-4oK;`bZyAY^#a8*>kkU`+!=zc{;QVjQ@yr{m295D@=Bg_S?qN>9z{fzhf9a@sZ~Kpl57snX zf*0QGn|FKT(Aj5T8zpzA^FVl~^E2Q-s*dym{d}f_cA&wer$CY@kW!MkT#S~7b40g% zFJ*tK9+!!XL88mry$I_E(MB7r#`+yM5M>wbvnh|fv~0sNa@k#T@gNmC99?8u z#$SqOyDh7E9gTZEl#-SXP_W5m)p7w>1@Fj6g4jfeVVAu5$Z0sql@or02AcJtXQ%UX zG~XdK+85y?Xug9u`g|EE;Cz9yEPqM7(=3Z=g|#)?(PQe%7a_j&p~v&c)!19frZ8zc z+)P3ex%bljz-!u~O0_u$(aoAkvMYqh@VAoGlr=9&Ynu6>&Sq<_ftrl^sq0A3a@OPA zfz}<+J<6`<^yk)HvyuEQqvo@kPe^}9*1!6twHwyP+H|cB1FMz(t*k#p<)r6;&$>8T z|I7VY|7wm4rv4Yvk?Fd^Y+M;x`eJxP1La~3bGees>uih9Ok1Em-nvM&q1pb>btId< zd{&W(#|Zx%-axT5D()K3 z`)wgdmLZ{%>E3{#7BpG8S-Phk6@8NBW@TIx?5{XM+6{GmH z%h^oQJDo?5?R0*HfB*b`r}JO?Xj4;X^uu1-tv|@`yxo79bMIx&eYM!*&uPY8;Az+_ z-PQc&71RuIQ&4Ewi$^YNbMQ8;W)2e#HSg8D)_gywIjHp2>^wR7ew~U6125!ME=n~4 zPlD{_;>2bdH_c^8^U|>m$JnJJBk1?hfnr6?>ujO+__p#Siw!4tiH1m*uqypITInND`o2RcrGQOJ)1mb6tMD!u122@mjE(W? z|5an;UQXrSfUbp^oKqOR2)Z3dYj7C}zeT%w z?q|mI2J|OP0MM*|dGDwCz&m)BBXYV4krV^hHsw06K}&P@fn=*D*op&Hys(D{Q7qM} zkM*22XyhKK2Zm)eeWX^N0=2kUyVnTKp;?)lpoMCuG@5r{SVl%25uJ_Jj83-SBcuve zd*gg^rO2dU|0Qg*#^|vA5eDfcM<$pJ7Td=$Jkfn0)-3CDV;uM$)U5!y(J-wY;SSP^ zpqy$6%5#tIg|-@gK~BxOd{`NMo^P!N;_(4KzaN+6ahu-#*vrBc9E?XXza_yZsab%} zqiYbZ!!Ss=8DRxiRB$~Fw%km~@S?d7u&0czH9AuZg8AD)K~&e-IdNefk2!-+%cBkF z5mP-sfos?)`tC})jGeMT3P4w~Qx-@m=n10~zWo?Q;78N=X$Lx?%jNw10-dbDi8FOy)Bv?ksjrwPI3LDX zuI@H1R#oMP4ycFWA*{;5ln6?1W-1kuvw`9Wg|mX<2!*pE^F=DMmJ}@_l39@rM5-f_ zS&>)ijB|jzr6LfGsR$&Q!Z+snP;$OJ+mD}$NS;S4QYOeqRcPLp^kwrM}pR%<69wue%PJq8XC!r0@;~f6@`3BLF+G z0fnGu!gD7LEYnWO-^tJ5tlha_Ce9D-Trdj|-nroS1V_3m?)QodATEHpTofrb-*&5fxOY>e zb|HwHBdV^Uh#jlt_n_-LcDegvmPDa)U?1e&Zpjh;a#{3UO2yc~fLC!3>zK0wM-$M^ zI!=M#b@VQ!#*eyv2}Q}FZs=SA@4zEh`q`Vs+u&xAoA}_O9r+a_VhMss+|2F2G;DiSO!YosA^FoARczu|KLO759ObIG%OzU4AKv*UAa4 zBUb(Q_a;G9&#&OQ4p;{FAJ?|&K}!X62=!g!X5`l6D@1DGd50CA^|`{N1Hjs|KE+?u zfbSMuiXAAFBI>r4Z!czAv)X#6`8Kj@jF_)t4RS*-#^f7G*JR;dC`|F_b|}94N4-}7 zd=&+#K=hz(BnliQ%8p1BNJ+XLCBmKh6wJtL*u2l96dc{S|5IE-_d|9#NbRhiqr?OK zijdzG)WjpWl>yH7dPFnqjhhh~aY2VX1>KFxL3dixW5i8hy&a#m8?1NYv-Zxty(tWX z&&EQ559Z#!Tc7j6+}ro+b3VYFfM)T#d8JwN8Z^I~y6D?Q@A4bAQ))g`8&aqIrRD=< zxP51}zWu>Z?3XsvEo115;(7;Rk)%_UMWPLx z*Hatz1Z41bOM3kB<{bEMO5&=s*@V#vE;1AR0+}58llT$~atkL0C6Rfl#(WYVrHOv& z0zZzQI7=nQ0FZBa&4RBx5BZUoRV0oRIesZb{uD&@*|;|qUG=?-NOUrZ;$0MGp?X>h z^$Bj!p;15L#KkqYQ%{e6Q$__1EUU?oYsr3(kiwPlswKDwHwB969sxD5uf$o8256O= zZMQWSq3pboxZML;TDk|EI;MLdFzG=uC21h#8q+-%!&;H< zf#RZj;E#SJnqSvKRMj<_usV;|HRxIJI9&tpiE(a@&q8N%Zi&xontOYLc6~mWdwZik z=Y!a;L(6n@jYjGkP24rM{%^ZR`AIEHk)op)cew81VS{Hsi5=Juh|{XlxJ44X0`1YA z$#k$eKhKU_oHw)p}VgpqqCUL@$&QXewgiWx$8=urp@O<&&OJ)gp_p zBDc!o^`*2Nz$A-iPbku64-gHzj+PTRzdui%*UMy{AB)07h*Q<3plDg`;U8tMH7C7- zgpql`5~|;FNGioC^{kj@1OM0*CCN>N5HOpUFwn?r4Wv|ArqpMk2xesXdP7uV*b**> zV1NUJ2P(?8<~yP@SVc1Mnb|bI!74!_O3rdq>7yH|(zD?{DTxl;{)tv=PM!q!nvH)l zXjbV)Kt zkguX>I56i;8>w6oo5Cx2nlpO>s)S_=q{&fo&~^ABg>B8wXr*us^a6;HkLV{VJkd%~ zX`YN(tV4Fl!Apun=^Qe-b^cI#foZO!OHea*@+>$W(j}e1eHherNo}>&=(0FWsVA2* zl|S&a(p5vRypy${IXpxqf$|Km_YHu0|KSC0<;`}0A62bqoE3%x%|-`9w+djO4?{Hw(nKKr4Ez_J8!NPBo0wG<;hU|E8v$`nQXQ`p=km&8wmNhSD)qfnS~ac4Oo4 zhaP-z#L?@A3?Exm{$Y+Dk9f9-V5&D~uSvJxzj*-rE!YPmlhCqW=u3A`)dMzx|XyejBMS zTUC81A4l5j-*?>(p|PXVuXgx-9>+9qnZsY{4HWtOGfp*nc#HvkFDFxOzH;FeDxG1R z^YVK`wB}fRU`5T1HzdzXdeb%aiq{UeJ5bks+WF4K?F)MxJL}YQwzOUPL)DjKjC0=@ ze)BJXf2-o6(;Qd5*e(C7`-e<=q0{Y)o?Dgir)!@awXnAT@1DxJ_pvX=`#+yG^n(S5 z_Lbap$(4P-@AX~L-GkG*jQB&Vy}6^`%bx#A)8sRrY1nb+9idkrpSaRfy7T!>ty2em z_}Ndl{XX$8J@2cW9UeDf_q*?0^N)ThS9e^$x$C8$zWnT8XPj7`(EFh$GCzLM`|#73 ztv+S$*%$x&%mQE8*jx8E|9NA1@khtq2Yo33O$d9N0Yx;m~ zdoEbF>y}4138=*9|Tk~>gVQa0^sM<%gn z_hH)TvXHkdSQ46PxJwHtbVLZERtd*?xKSNL+4jP;SBBSl^U-%HP8j$%Xyn_C*~3(v z;P{aWdMfpshaT)g{ckJyTPY^s--th~STVf5uEU7>y1L;(f8lg5qL>q<3!wJiKp86P z=v5wo{T$Bg@|@!?x!Q0US-I3XbG)AEC86NejvgL|qgNJL5lJpVW&?)hmIOn7pJU*# zaV14QuVdV>ab9W-M>M}VxXz2#b%z{2uR9oW40M$H+%vs_pfM^f z$LScKmo|=5j2}DpI-^J50Y#3Wx6oha5q{?A>qd{ya%LElO3KQ-0n{Q$A#Mhrp$;nc zrlyjOlx-16X$4V4EShwG(iVNH^Z5VB_aQV1MA(z+7Mow(QD*KHw7I9N-!t-R0W^d<=LD z_$)95`)uog{ejzoxxlx9<-pd%kRF%`Tm#$&Yyx&3j`YCAz!dCw4jqB?zuG}0eJhEaQGqKv0kL!^hI0xth9tG9`Zyks9z)ygUz{2rJ4?K_uyW@i5853c5 z;2>Zw@Kazp&^rn32K*7Y23Vhub_2c%JO=DC8R>C_wh-7K_%~oK@JC=d@X8yI9#{cf z13Ut30(Q9(>49m$lykAA4eSqm3z!T1H?SNy^CqMR{tR3L^i4r};BCNTz=wb-xCh|M zn~@$k0hkM%0W1eT3S0tw8Mp@cKClUR7u91GfRQcc9+Ddx33mm;cN+P;cNLfEmD-fF5ARPShKi zv1Fi&~0PXd2w9vj8K*5p3|`sLuK zS^2jyo6Ghp=y~8{_>Jh#j_~(^>;ylvorT{ztsO~FJ$}Kz>1Tw)7q_wSN7uKmYcnsg zb(c1*$N^S&*yEP7!{G-zSmds)Ydx<`>$^HYsw>D@kook&aF`y7H|yHk*_xC%Kq?0R zl%8TsZ3{PN0j{z~9b`^}OePVuOhOt9Q68(o9{~Q0D1N}?H-f(i{OhCm${>{fDEJ$} zKh4U|RAnH3hXlks@VNnX{!=3S-rzq4KBg;?^5sPM+2Ah+AJdWu|GEg@2YxO1!>s(O z2)_>eRp67E&H9gu@K=Ms6nwk#H-bM0d<^T6^y4GxkAgn~d^>;XfVL}cK|j5`aeNE> zZ19J-!1sZF3HSr7M2Mz_+tcBlw-b?`=&#Et38y_zv*L zTlop9fw=wA52$``Me%pC0taZl!T%5V2d#YPyw(XHMe@J{iN=)RKEV;zt93d7yO^VztEcA(uc~y{{{T%R(?W}ng0^-M_p#0{~GY`1K(jye<(HUdL^DU$w0{$ObNS_b>gWzL| z8fk|cBKcQ=za0FVto(#YEI{R74*qWN?PBQ$@OQPq-wXaG@aSJ;aAg!<6@d7iF}JA%|Ai?2Gp7C_;J4`$F51GS|iHiAD4jETS zrp>9SZ{Kj(g=2HvC7B5kJ-r|^9x|n|GKCSDY{)zUnHw*4Nv!-ik^1!qe--%Sto#Hu3FP{N{}K3DT14t!97$ge{ukie*?$T6 zhrzeA-x~1$VUym0EStdp9Q+2%^~*YOtR<6UU}ZLddj1rzFGp41BvW zauE0*f^S!cJn;YC0zU-)$KYf8D3X6(#70ZO-_rtrE%>w_YnOgE_zmFO`M`JJpFcPp zzSo+6LVcwEn1C(;e^C@)1r@U2An;Fs|D)B$t-mAlav#nE|Glda2cpuePC)5H;GcGN zOe{%IPD1>p;OB$CAS!*eS-!R4Z*C#|ZtzdNCVqMcbbbfEAN<+Y7=(D&I>cd!>(Ci{ z%5M(ET-_?uy0;mphJgRkFnb@!2Y(;<&qdYYDYFh$;7=PK4lj)2&o%kW!G9b4rBVF5 zP5uV((?;0y_kzC;{GrzLGdMZd?*#bofuC#TC#b=P+MqKArB#FOM#{Gw{Ks41ZvdYj5w!F1z2Kjj77q8d>Q~czRe$ii zf#1u@m$8iNk454o;AdI+3F&72hJb%P_#>_S+au-A2R{h@Kr6pA!mk2<0reB1Vy^gXTm-I4s4gP(|NAa>)x21$=C@JRV5M$+#E z|0m>c7sF40|26n_`a5IdeFS_v{X@V%1ioGQ^T9vbLi#H3zX0FPKFh&B+yZ|C_$`%h zFZfhGyYim^|9}nO0h4yd!f7A)@%jguoP2C3WcEX*KN0l&)iM{l3H)O%@NWa(KpXe5 zrnk%$9sxfI{2^9;Fw(}ag5LxDrB;4IBu4H7|3UEGQT&@sAN~dWkHCK{ivOU=?~3bN z`572zqsD+vJO((BW+?dAJHz2St?6mrY>5L?A+r`T^rWk4qt@S<<4865^`pY!<5nFB zUz=rE0e)dtIJ`ZI|EkH~27a5-F>8Q?MJ9hg__u;TCMvz!^rJR3&c>V_{L(0XWGr@o ze;EAfQT)h$MJo7%u8r9fO8C^we=7JVz>l&~!W|~R8hqCn>{UnU-)iz#fZsYh{(8WH zG~0w9Z=Fagsg4IBb0KszM76=|W*f9YkoZfEeP8Jg{u?duGr->lexE4)y-fWc@ZY}9 zeqVP!_}_wG8kN4-OurKR)ZF;}#DV(l06z!(6Ikn+K9+^~%#l+}J2pe6+YR=0Y>$nQ zuHeV31Le^R{7Y_(Kc4}c?VJsnHIT{0KBHNV)?->zA*daE;P1zE%ge9_7vcBhjdtSK zfq$eRem#l58vGOBe}ZE(eZq%k9UH-4Ulr;>&d1akAnZ5$9_+y118kpf)96z zq*qINE`M+E4})(P^RvPK7x?jFCf6VQAHWYr)xRuKfAFs@35TDt=AW?Atlw(zKLGz< zQTF}Pv~MH$e=J2CCs@kg`fPJveiZyi{r2&)0|vv6<>BzwsQfpX`S%8Y4fr=j>7Qik z&j$Z!ARNw#vVWFoe;@c=Lb$FNrTp*_A7X0cK_`69z_`#_1mzm}N4*axv_Up4w z$mI7e@CSjv4t%?qod^Ep`MAFzD*q#9{vq%$Sr86C9>ssidAIO(3;Vxm2%v8wKJQxmtgJaWYX^zm$#bKHGkeT#QINZbPPo(E} z>{9HLt00sAaQyWW<hfJemoqW6UCop z@*Bb527U)?d}!T{g9Fze{MAo|>BW(e@~Ljk_UUi|=8NE8W93(}dgk{Ae=YcFR=(wW zbT;^Z1^)^wzdVxO2mY7f54Q5vmNVUdg5SFZ{!#Epw!rV;fRDAn z?+t!g3;b;G=eNN3f&Wko{5tU0w7_2t{>~Qojo|NVfqxYIZ(87Y=!W|pR@vLXH~8nb zz|RJM5cneu{PcQAxhJ=;5B%}q+wCdTf&Y8()2-<(*F08(|5ywBM)0?{z&{H9!4~)( zF2wyK&)DbR8~jUK;AexM13tD_BlTBX=c@kTUj;t4-y;0ck^WH!{>9)YS^3gG9iXiS zzZ3Yw6D;W|P7aF1+a}2LeGd0(Sm#*k8bm!!v`Ei)ka^-y;qa5KV)Q($;w{N^z6key zyofQ(8vm7^gERtkhs;pO99tU>cmT>qLt4!MP!9Qyo?%9jANo~X?#G-vEQZeef3HXaOto-AX z-~Rk&f!{3fn+1Nez;71#%>ut!;Qs>)?7v!7a*1GTsdI{hW=U#(M0lp0x1|&O&XIFm z&gGv&&Q&Tr$3bgh{PcN!%=v>c=TF3(KQHGzrzb^xu?N>>=or67deJ|A*jnUYZmBxQ z78C#43u4=ifBC|v`6&G~|NH-h{dC?W6~}Fg{7c1kD*EaAo)A86wrF^)pg@4%M zHL2V ztNMV%qW_WeDoG!fb6t*Oa^6=A_oJNad|CPbgFh>zy<)Q-EPjwFI8m@juu^cL;4;Cd z1lJ4h6x<_tNbtB|+W{))vjuw!4i?N5oG4f%SSh$raGBs!g6jo$3hog+BzRn~?Lf(2 zu&3Z)!A!x4f<=Orf(r$g2|gvbUT~-29>GI`#|7J7F8K@g6dWv=DL7HENU&0Hq2MyX zrv%pv?iAc3cu4TLVB0~GzhFtQ1@* zxJ>XV!S#YW1@{OZ5j|;XP zBKZsU6dWv=DL7HENU&0Hq2MyXrv%pv?iAc3cu4TLVB4!Cf5D!Dg9S4MCkhq`Rthc@ zTqgLG;CjKGf_nrH394Li4{=BWJP{VXPS2?0cX-6t#s65e?!>|2S4o`eIXR6Z2I6`+ zJSrV;+31C>jY}K@uIM|k?*PZZ{sXTVFkpamYOUOYluH}q>#&8yhcwP5V6;6f^MXPV zpkadO*}PeeXC8#!2iVF;HuStfk9%oMKg*aZ@jON3CyBhCSI|BT{e1Y>$~em?kCC6p z^kj41p~vMXM7}yk{tc1W^AbHz`AFoK#K?aQdOSP)2Xv}$Ar%T-zecSnJCZX$`4eG2GQ&S#{Y zGo+kUP0Dv)jQkv-?~kDuJ4n8ZaZLPAm-8s*4@5r^L!Z!1(G9VKmcLudGnZ#qxK1P! z4!U+pdfJP;w$F1?o@ObJ*0V|Uym^PJ&}~w$H$>jJK-sy6&_4h@UOVm=`Bah5M?LBH z57FPeP@PM+;%C;#o}KTErhhB)Swbhjr{8~s{>Me?{4oW_JUAZdNv%-?zNZI=ZD9|h zPrgghMV-+_==+7v_wPXDQlT&53(UB8MOLNB2o6na0Q^Su$^{Ur1;FDXI3=K+VOqkEJ72^$rCsRE;$ z&{u6z1inWBhl7NE^e>8@Bl2mW$FoC@4gDt2DXtn6Sm=lH(a$aVOWsrGk1H^yvHZEl ziV><`R0+L8=#JEA`g|=fbZwudLT?bd7h@RXjO3%ZR_a34D@z09zjL@5f zzD?q-_P1|s^rxWtI>P^R9m+lsN&s^Ry+70C!~*Fy^a(cfV$uJklv9?+ zMnLFwKP%;YO%R8734PrHMc4ZdPcz+85IKC=hW@4v{X-l2A*Lr8S@V@**)}k~vyo41 z8{bd5Fg?jA4k|sBQqI9P^64T!+@%C*-bTMkLf?Nvoy&5<@C*I5fTEwHz^E1a+(Jdr z`#p~{U4$&~qR0>Er2K)e_krJFBmZ}iPg$sx=ys&%Fv+j7WBjUJJLOm20wv!`^6o73 zcV*z+E_4Ue<-`J)+R(4Ep=XHxhp$me(nQyIq4#qug0`Cn^c0MP=SiH@Sx>joKih_0 zC;F2`ze5x~WF!Ba$UpLf64dRxUFfY-S~25((X&VB_fA#>+4eHNw9)^g4Lzy7YM1x# zReJcIS{$Ay^daIuXG*>Um@X$47-mBsC3>#kqJ--t%_PvtPx4~?q}WE!tv2)rx)_E7 z?X_R#&$>wSCExOpqAU`|<2HKMfKKHxV#>2s^h}fgfY6_f85e%B(Q^tMDW3g1G9CJFSNg@= zMn4<*5hDM_Y$dPf?QR?Sa*?k*L$w#*Z-S(M#=uJLIBb&Aqy6~~$Wy<_i0K#i+US2m z^gk!%U#Qd>4MM+L;#<1VciZUs!iG-I*~YV58#F{b`WZ}5GM<$9&{^`mMCiYWU+H$d zTIi#Sm0+pJUn}$@G7tP*=r;-d!sCjd$BQzii;xB8+R*Q_p+9Cre-`wP306p`=a#o_62S<>_H zO^Uuw(p_H zNaml9ivCwH5L0~{o=^mSHUo#-L_YAgqRV!M(J1ut=M?=D1;)ohKlQL8@O{QO{EyHt zyFt<468SSQ!HDPQJ(!+koFn$setwnEYkyPPRMU8Pb(e;T1FS4NzVtSHskzWbxG38nt`Dr3QASOQ7+Q>g3^21}skr#!Y8e`90 zLLXD5%2O(J_+024-HM>+MgL~H2wC6^46NkOpG&)6E4;x%PZRrB3f(F6IY$*iw#kgi zLjTLZ6`k)F#o;YNZ!hsym*+O2KRZ*&%XG?kNa*P?^Q(qw>^@{_bQ@}Wd6_ZHbA~znXbxi{oB`uewFBP&QsENNTO?n z-uG=q$PoHCp|6#3oZp=So>%BiH!Hz1k*9ab;{TI=3vKB4iJp|NRFXF(<m6YACv(qTj)(fUnvC{C-fsiPm+r2xZO&|op+^Q=yCjaLcjTlQt*Z7xkTveWMZi0 zM=@Q5ERZkqtHrOey9^&{OC~M1F{j*XZW_`;XA;JnB4O=%;sz-|pR*o@CTYN7wrj!)@fVZRlfU{q@&B zs0_9J^F@F5kBUA*%5$sG^Z%&`{LBpw=h*14wV^K)J?joBJ>Q7_=Y)PsOx)Wd^!sIf zI$q@85&DYBiqJvm2beA=7WhWwdyAj+7hc=5RC{gNqX;{Ney-5Z{z(xeTpE3Z{<<{O zFp(dw<)ultAiF~EBi=HoiY$JbeJJ>S;Qrf_x|AdWv;@R>2 zyt56xyA6FX)02z~e^wdl_R19c8nF+|$iK;;(>lcwvybN$`R650E*1F@)8)hhb8YC4 zrzpRANEGP)b+_oBAn|jg==y`u$B3PEIoAvQ2Cou)LFC`D$@lLz^k&iXiCoCl{@D%z zgvPt0_b|QTU(iO+ zY>{t&Ldgvl`3G&}Ul932QvP)!zgg%TsuV%bhyO10vt_){`hQ@$B(}h*U6g$`OM6`) zJcrPqn5qa8rj5%kQu6=4U(qua82wmYPAqUW=pAuAQTOZi!ppId&zF1`zoP_$BJZ)0 z4~qPK)0Ln@%#OTV~)hzBoXOrBcCSnnKAx9!A8DV za}slZqTI{A<8Uqa@06p(+q{EDK>^OlYNeKz!il5d;MN>7e5lktPl_e;Oa zmWqdE4gGe}KXI~Be46OLN9cQn zK3?cg+vuThTyR`{UkUO%C~)`}p|_U)b-w8N!bZ;zB7bADa@bWOe<~7^{tsjyo1f6mM1I!<4u=Z;wtPj`ex5D#{<03O6kXGVzV8}E(DQ^@LjOwW{H}b6 z+#~eIq`rFo^Ssc%6+`m-?jip&(5tz zPmPJUXM;}m{O~I!KS%UjD)jsqKOZXeyA~?J--&#t&^_XxdiV9{H(7it^IX#|^X1bzT z|Gv%iB%^7mqU!SBDD?AU;?gXk7yY2*^?YeD(-qD7cLmdvj6b>+Rr~W6rduV|aRbwn zj1uvG4D0;+Oz3CGx?`5mzY+R_GJnwJY1>WNr+-X2&l7r%_@VZzD}{c;D5aRcIf270 zp^uHRgInnP3Y7d!3XCeI%ZUZ%iTtNODWZo5LkIwf)&>fQ23e6%vQ|g7~1OI*$dSkOXA1?GxFkrlK>Qc}hB4dRN zmQON{-=L@xUX6)1dR!v^eoQ-7+Q?Vi(Cb9chjl7n2~WoTHu8_#(En^hPwo`o4qGMP z8)N$QTS9;O3ss&Tt;8Ne|9Fc!e^!BULg>qVijXbzGcSstZ%?Kt8S|tc>3Ewi^u;mp z*(l`CyY?+Kf)yo&F8(&E%Z+bE&a@JOp+H4ph$yatl2EC;+%+E36TO9(Gc7c;+*?vs z&3gGS_Yv93aY1-^t^R33Dddhr=aS7k}Cq`>F3 zP^v7)GmWB>GOI45up)pjZH8Pv|1_7+Tj})~evj8xQdSZwar;VUc|As%*Bx*bBFh34 z#Bc@NWgdU2E65ch2!vdrl2Wg$G-z--J~JvRN<6NDilQQKpl^SwI91Oztq?VwR%nz2 zT{BVeX>@>3(He#BfCq^^-YTO!;4j5TevM$EyUgV;xCMrH6_t2>9(?z+&{bMe=0|D# zJ|E~tsXH(|Mn;nqFM#g_2aQ61X?aD+>nidGW*{ScQ`D#^_n<*2Z&$g$gg(F-)HF&H zAyQg5ia@2r7dr8|Txui!awi>x3S6F`I`9Nt#qMCSD~Ru85(DR`OMw@<@aa)Fj}T$$ zfpS>tp6*qNRNYmoU`aVEDwrAa21OEfn(m$H^4?nE_6e!D6m~&l27P`wBMhW7VGPRH}dT_fkCEuMi72zvu$ zM$r!!geK0h%o{>((_CaJ0=66uN@tGt0Q9#*r@2Z(UO3J)7pz{YI>|KU(;okfGSJl* zLa##?g84(PGJjbCK9xM3+K@AQGH!z zom^-KeAToJ&WA=G9USAGnL*_o9nA6LYpf$HLLq+{Q@C)RfP023SYGT6p!D2A%0oi* zU0bEJsMK9mgaYGRxMhCyLiH8ssFJ8}wz|^CjU7MUHD+}B7?o1@Q^PaT@0mHu8@j#% z<;ACV;c3MvMlecA5TDZx`J+kX8s4h%B5g^R{st{MZYgeq<3R^1sv{1-hC_c#6r@-wln}&#} z{ZgC{&dMLuRdmvN3zgqBjVjsEi>}GRiR9O0Ogd#=rt+{mr!B|cPy6|@y{)REXv3e z%IMRTsoGLc~rnuca4x$UnzsI1M+xSX~~9vI3XZbTx`R0a8nS3m#P&CF>eJ z7ULj>73x0-{ZvsE;3!t0X)#y@T({8Ymzfu7Q$$)cLR9d;mz(i@WvzxvFoR5?9g;uX z>>8gtE^V@F)VQ(YB?yva>tM0Jf<%4(ptPK6Jr#gm{xa#l20r@k55(zS=;<`3pu&}^ zQOqLqAYXw2iRx#XMmw)b4VILa`@Co$ccE98nmjfKNqm`zD?Hq&#-X`{8ZSLL?qCq^ zK%K;+!Ur9iG2pNKu{K+1r<}gM&940Ulb2Exu+C(Qym-;Kct^m(o&3-ni#1oQW z>Gsncudsr~Jo!qvB^1H*C)erZJjiW4dag|IDp*_*;&{Vj6x!D^MtQV_VaBq;@|jT? zcsKxLNMvLa#&jE(UJY;~T6|u4a1r8&G8{**LTOvpWHEh}ffhrtqcV3F_yf@tb`Kuw zc-Yg?(S^yM9}~DTj~-u&O3;!RJH%7*MSB@ttv>6LZ`>P2g=L}WSqGO-Pp3HAGey;* z0+Y!ZFfYET?hUbl^86}bs+ywz&#**SaUUo7g!BWlimRxiOy-5G)*K;uG!62oJxMvL z3as37Wqeg#GaX?KlVX)C&-BfyVC5i1t-Rdj_IRQ?Fa=J<)D@&L0-=%IrlPC_({s!O zlm&SGQ;6{^;J%!e2R_VaE~g&x+G38HKQb@Fb5FMUoWF*u`te=K6Z4r zYn(H0!no_K^C%}KVKWU1J|)!TnE@Ci4{I%~Xo5D`O~Vum{-tIVE;MNYdLb{;O5G)q zSarEH)5&JwI3X(~BL`y|3rV%ZugEe#tqP4M{@4GVsD`{D}eqaiibaYZ7Awa`>VOfUU~ zC1?pQv^gfJ83~Q47%!%KPqt8bNigL1slX2Z;Ls|KKeNys47uFyf&!(~QxbGz+3vz} z9P>G2d zkRAiY3<*oI11Dx{x}KIOsk@RaeWKfjwZh-HkJ17&;15Jg#P$s}2NQRf)lKY_#@YsI zq{pnkil>MiG_qx-WFWjzbT$`M)L?o)f{jvnxiWFY+030Jb_8mFb7mWv)JucW9MQ7= zV{a-c!+e681QCuFuF~>mZzsRBtW3BR#FTo#k;}YTzW7U3!6X#W;sb}6!*T-C&1C9^ z`I^ihIl|FGz!ZsiBK$}i37=Ch^+vZt%w!!Cno7ycEV?Qpo0$V#mWpqBi5Z#rQ=w1R zZk9;(2+N$!%z=!9mc(i*C=r_)2I0R-Z5(msOK2{ydSnp~?I&l2HGm2o1-$6ya8L7Q zUX+`&Xb%1|nz3e!SL@ZTvNtm@Rp;fE9^l0ZcnBry>46K2UU!+^Y4Mg>yDsOV<{%WY zl=1?a)^Xh7c^!}442s^T`v zYYp7yY>f5jKu9|;vYn?Q1o|=!o$TQ(sd_g1K)PjJj3FMIaiu&jHj1$AH9c-CsgOoX zKU$-@D67KhuEOH!t|E6yWcD)%OSe3DRgl7Dk%2XHC}wW0W+S{{G(BMBy z8HRhJxwGkV<>jQioY!TzT-195UNtAC4(#MMRI6ztLj_|wV-rXp#i~g!uHk>Qq%-}F z#Uf(~Y|2ioS2MZP>D3O%DC-W$$)lPcyrUYaFyIejwp3yUe%fhN8-zvv@)$#LN^;Qv zZ7X3op`k6xSY|(!jXC&Y9?zpPbH^*o>6*}HfTBd=CofyEA;I}r12x4{MI~qEsR*?I zZPzHWY(WI{B9|g}I;ORH#gRFBoYOTfEzhaMY3MG6Dog=6uh>4%OAC|>aTvQrMRB&b ztnFrvuF_{qgKoELP?g$JQ9GTSktIsVAWrj~sD*ki`iQws=YgbMZ8)Es~(S~4mOO`R8m#aYNLR+688bu0~TA zYNiU_YK-axCpDGsc`?1iRSXawdh8@{5pB02BaULswBO@$g_vPLUu#OZPawxBti zzsTc`-V8AnamKu5K_geB8n|3x%E*9HTrT@q8L}3%Y|m>FTHZe76wzYSG9^>v30(t} zeWrjf7rWv)7DrcGe^shSy9P>sj@=*N4L|fqy7CaI!j-B9vTIxfn_%4HN?A76saK5S z>yN6nn6KwZ)XI{A)>dldV+k_0dC4F;7W3#8JuhOC+S9?_AkFkyNo-J%nKJWeX|1k@ zUpQ-&rc&=eNQs*@{F=`E#; z|H{qPU^d=de2^hH;>gH2R9_`;^?_%hAHr2&;!OpR2F%lJnZ zCiHGF`->T?$V1IoMTIvzAnj4eaA`V%c$dezVQ#4-Zc*zSoLL%j7XU*6g~j@)4EI~~ zomN)S7ngmpe}idPUz1c&QR4ITLGb8{SqY6&EJ&BJjlQ0lWl*Xx6i^)8AVC*9qfT7l zp?|n3fgF|lLZlI!7=6iIjJ~|b**D$l0p;+5rqEq<-Aosn7|l)af=7_0=bLne-CtDQ#<@i5Ec483mQ{} zuA#j?5bO)IreD%pF*J6Mk#7@oo`!R}bxY^JR?=(4ZQM~mxy{R@jmFV&ymMTqZ<6#H zb^f>=kbkk|r`v()-cOxgzo%VeR-=?kUH=-$Qu>jU2tWPad5!u#^^~5{#HPOwG^!$%kAM1kOF@0#oo2`S58bC} zP4AF&8k{PddocuaczT>*{yerzotTmB~{y|%x8FPFwf z(=bYdLn{)b+nTBVI=y}mq{d&g!9_kc|5swtH}q2ur!mxB5(>nqGu{B*+W&{9*47;@=_#Q4SLr_VKh3K{C6I=%i*L;io6{s_`iPCC7Q&-7GD zp9-G!r~O9LPT+u=NT=8D+gu^(9nvtm8Fjg}o>s`*n*TQW&cimBs(&w?k)Temv29Fx z{a(*zNq-`y!F4)~^q7Y=fBk&levit3m`(ngd=|t=m#)8lp6Wb#5y-YZnxj%UbV9*% zCI9|0be%r7|Ep4|GE)*d*S>F6F+}or3{X@&)?(!0pRTu#UlbmqOQ_`Ym#Lg4T%^wR ZU!A``r{_`PrGI{-lDsG;LCmS~e*nFrx-9?z literal 0 HcmV?d00001 diff --git a/game/client.c b/game/client.c new file mode 100644 index 0000000..909617d --- /dev/null +++ b/game/client.c @@ -0,0 +1,393 @@ +#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; +} + + +; +; +; +struct GameState { + struct SuicVec3 cam_pos; + float yaw; + float pitch; + struct SuicVec3 prev_body_pos; + struct SuicVec3 current_target_body; + float interp_timer; + float recoil_yaw; + float recoil_pitch; + float cross_spread; + bool scoped; + float fire_cooldown; + int shots_in_burst; + float burst_reset_timer; + int client_tick; + struct SuicDirectionalLight light; +}; +; +; + +static const uint8_t sui_bitmap_SuicVec3[] = { 0, 0, 0 }; +static const TypeInfo sui_typeinfo_SuicVec3 = { + .field_count = 3, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_SuicVec3 +}; +static const uint8_t sui_bitmap_SuicColor[] = { 0, 0, 0, 0 }; +static const TypeInfo sui_typeinfo_SuicColor = { + .field_count = 4, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_SuicColor +}; +static const uint8_t sui_bitmap_SuicDirectionalLight[] = { 0, 0, 0, 0, 0, 0 }; +static const TypeInfo sui_typeinfo_SuicDirectionalLight = { + .field_count = 6, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_SuicDirectionalLight +}; +static const uint8_t sui_bitmap_GameState[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +static const TypeInfo sui_typeinfo_GameState = { + .field_count = 15, + .pointer_count = 0, + .pointer_bitmap = sui_bitmap_GameState +}; + + + +void suic_game_init(struct GameState* game_state, char* username, char* server_ip, int port); +void suic_game_shutdown(void); +void suic_game_handle_input(struct GameState* game_state, float dt); +void suic_game_update(struct GameState* game_state, float dt); +void suic_game_render(struct GameState* game_state); +int suic_main(void); + +const float PISTOL_FIRE_RATE = 4.000000; +const float RIFLE_FIRE_RATE = 12.000000; +const float RECOIL_RETURN = 18.000000; +const float CROSS_RETURN = 14.000000; +const float PISTOL_KICK_PITCH = 0.010000; +const float PISTOL_KICK_YAW = 0.004000; +const float RIFLE_KICK_PITCH = 0.018000; +const float RIFLE_KICK_YAW = 0.010000; +const float PISTOL_CROSS_KICK = 2.000000; +const float RIFLE_CROSS_KICK = 4.000000; +const float PISTOL_SPRAY_GROW = 0.400000; +const float RIFLE_SPRAY_GROW = 1.200000; +const float BURST_RESET_TIME = 0.180000; + +void suic_game_init(struct GameState* game_state, char* username, char* server_ip, int port) { + suic_terrain_init(); + suic_net_init(server_ip, port); + suic_net_send_hello(username); + suic_disable_cursor(); +} + +void suic_game_shutdown(void) { + suic_terrain_cleanup(); + suic_net_shutdown(); +} + +void suic_game_handle_input(struct GameState* game_state, float dt) { + (((*game_state))).client_tick = ((((((*game_state))).client_tick)) + ((1))); + (((*game_state))).fire_cooldown = ((((((*game_state))).fire_cooldown)) - ((dt))); + if (((((((*game_state))).fire_cooldown)) < ((0.000000)))) { + (((*game_state))).fire_cooldown = 0.000000; + } + (((*game_state))).burst_reset_timer = ((((((*game_state))).burst_reset_timer)) - ((dt))); + if (((((((*game_state))).burst_reset_timer)) <= ((0.000000)))) { + (((*game_state))).shots_in_burst = 0; + } + float k = (((1.000000)) - ((expf((((((-RECOIL_RETURN)))) * ((dt))))))); + (((*game_state))).recoil_yaw = ((((((*game_state))).recoil_yaw)) + ((((((((0.000000)) - (((((*game_state))).recoil_yaw))))) * ((k)))))); + (((*game_state))).recoil_pitch = ((((((*game_state))).recoil_pitch)) + ((((((((0.000000)) - (((((*game_state))).recoil_pitch))))) * ((k)))))); + float k2 = (((1.000000)) - ((expf((((((-CROSS_RETURN)))) * ((dt))))))); + (((*game_state))).cross_spread = ((((((*game_state))).cross_spread)) + ((((((((0.000000)) - (((((*game_state))).cross_spread))))) * ((k2)))))); + if (((((((*game_state))).cross_spread)) < ((0.010000)))) { + (((*game_state))).cross_spread = 0.000000; + } + struct suic_vec2 md = suic_get_mouse_delta(); + float sens = 0.002500; + (((*game_state))).yaw = ((((((*game_state))).yaw)) - ((((((md).x)) * ((sens)))))); + (((*game_state))).pitch = ((((((*game_state))).pitch)) - ((((((md).y)) * ((sens)))))); + if (((((((*game_state))).pitch)) < ((((-1.500000)))))) { + (((*game_state))).pitch = ((-1.500000)); + } + if (((((((*game_state))).pitch)) > ((1.500000)))) { + (((*game_state))).pitch = 1.500000; + } + float move_x = 0.000000; + float move_z = 0.000000; + if (suic_is_key_down(65)) { + move_x = (((move_x)) + ((1.000000))); + } + if (suic_is_key_down(68)) { + move_x = (((move_x)) - ((1.000000))); + } + if (suic_is_key_down(87)) { + move_z = (((move_z)) + ((1.000000))); + } + if (suic_is_key_down(83)) { + move_z = (((move_z)) - ((1.000000))); + } + int buttons = 0; + if (suic_is_key_pressed(82)) { + buttons = (((buttons)) | ((SUIC_BTN_RELOAD))); + } + if (suic_is_key_pressed(49)) { + buttons = (((buttons)) | ((SUIC_BTN_SWITCH_PISTOL))); + } + if (suic_is_key_pressed(50)) { + buttons = (((buttons)) | ((SUIC_BTN_SWITCH_RIFLE))); + } + if (suic_is_key_pressed(70)) { + buttons = (((buttons)) | ((SUIC_BTN_PICK))); + } + if (suic_is_key_pressed(72)) { + buttons = (((buttons)) | ((SUIC_BTN_USE_MEDKIT))); + } + if (suic_is_key_pressed(32)) { + buttons = (((buttons)) | ((SUIC_BTN_JUMP))); + } + if (suic_is_key_pressed(90)) { + (((*game_state))).scoped = ((!(((*game_state))).scoped)); + } + int my_id = suic_net_get_my_id(); + float view_yaw = ((((((*game_state))).yaw)) + (((((*game_state))).recoil_yaw))); + float view_pitch = ((((((*game_state))).pitch)) + (((((*game_state))).recoil_pitch))); + float clamped_view_pitch = view_pitch; + if ((((clamped_view_pitch)) < ((((-1.500000)))))) { + clamped_view_pitch = ((-1.500000)); + } + if ((((clamped_view_pitch)) > ((1.500000)))) { + clamped_view_pitch = 1.500000; + } + if ((((my_id)) != ((255)))) { + suic_net_send_input(my_id, (((*game_state))).client_tick, move_x, move_z, view_yaw, clamped_view_pitch, buttons); + } + if (((((((((((((my_id)) != ((255))))) && ((suic_is_mouse_button_down(0)))))) && ((((((((*game_state))).fire_cooldown)) <= ((0.000000)))))))) && ((suic_net_player_present(my_id))))) { + int wpn = suic_net_player_weapon(my_id); + float rate = RIFLE_FIRE_RATE; + if ((((wpn)) == ((SUIC_WEAPON_PISTOL)))) { + rate = PISTOL_FIRE_RATE; + } + (((*game_state))).fire_cooldown = (((1.000000)) / ((rate))); + (((*game_state))).shots_in_burst = ((((((*game_state))).shots_in_burst)) + ((1))); + (((*game_state))).burst_reset_timer = BURST_RESET_TIME; + if ((((wpn)) == ((SUIC_WEAPON_PISTOL)))) { + (((*game_state))).recoil_pitch = ((((((*game_state))).recoil_pitch)) + ((PISTOL_KICK_PITCH))); + (((*game_state))).recoil_yaw = ((((((*game_state))).recoil_yaw)) + (((((((((float) GetRandomValue(((-1000)), 1000))) / ((1000.000000))))) * ((PISTOL_KICK_YAW)))))); + (((*game_state))).cross_spread = (((((((((*game_state))).cross_spread)) + ((PISTOL_CROSS_KICK))))) + ((((((float) (((*game_state))).shots_in_burst)) * ((PISTOL_SPRAY_GROW)))))); + } + if ((((wpn)) == ((SUIC_WEAPON_RIFLE)))) { + (((*game_state))).recoil_pitch = ((((((*game_state))).recoil_pitch)) + ((RIFLE_KICK_PITCH))); + (((*game_state))).recoil_yaw = ((((((*game_state))).recoil_yaw)) + (((((((((float) GetRandomValue(((-1000)), 1000))) / ((1000.000000))))) * ((RIFLE_KICK_YAW)))))); + (((*game_state))).cross_spread = (((((((((*game_state))).cross_spread)) + ((RIFLE_CROSS_KICK))))) + ((((((float) (((*game_state))).shots_in_burst)) * ((RIFLE_SPRAY_GROW)))))); + } + suic_net_send_shoot(my_id, (((*game_state))).client_tick); + } +} + +void suic_game_update(struct GameState* game_state, float dt) { + suic_net_poll(); + int my_id = suic_net_get_my_id(); + if (((((((my_id)) != ((255))))) && ((suic_net_player_present(my_id))))) { + (((*game_state))).prev_body_pos = (((*game_state))).current_target_body; + (((*game_state))).current_target_body = suic_v3(suic_net_player_x(my_id), suic_net_player_y(my_id), suic_net_player_z(my_id)); + (((*game_state))).interp_timer = 0.000000; + } + if (((((((my_id)) != ((255))))) && ((suic_net_player_present(my_id))))) { + float interp_factor = ((((((*game_state))).interp_timer)) / (((((1.000000)) / ((20.000000)))))); + if ((((interp_factor)) > ((1.000000)))) { + interp_factor = 1.000000; + } + struct SuicVec3 interp_body = suic_v3_add(suic_v3_mul((((*game_state))).prev_body_pos, (((1.000000)) - ((interp_factor)))), suic_v3_mul((((*game_state))).current_target_body, interp_factor)); + ((((*game_state))).cam_pos).x = (interp_body).x; + ((((*game_state))).cam_pos).y = ((((interp_body).y)) + ((1.000000))); + ((((*game_state))).cam_pos).z = ((((interp_body).z)) + ((0.000100))); + float terrain_h = suic_terrain_height(((((*game_state))).cam_pos).x, ((((*game_state))).cam_pos).z); + ((((*game_state))).cam_pos).y = fmaxf(((((*game_state))).cam_pos).y, (((terrain_h)) + ((1.500000)))); + } + (((*game_state))).interp_timer = ((((((*game_state))).interp_timer)) + ((dt))); + if ((((my_id)) != ((255)))) { + float view_yaw = ((((((*game_state))).yaw)) + (((((*game_state))).recoil_yaw))); + float view_pitch = ((((((*game_state))).pitch)) + (((((*game_state))).recoil_pitch))); + struct SuicVec3 forward = suic_v3((((sinf(view_yaw))) * ((cosf(view_pitch)))), sinf(view_pitch), (((cosf(view_yaw))) * ((cosf(view_pitch))))); + (((*game_state))).cam_pos = suic_v3_add((((*game_state))).cam_pos, suic_v3_mul(forward, 0.300000)); + ((((*game_state))).cam_pos).y = (((((((*game_state))).cam_pos).y)) - ((0.200000))); + } + float terrain_h = suic_terrain_height(((((*game_state))).cam_pos).x, ((((*game_state))).cam_pos).z); + ((((*game_state))).cam_pos).y = fmaxf(((((*game_state))).cam_pos).y, (((terrain_h)) + ((1.500000)))); +} + +void suic_game_render(struct GameState* game_state) { + int sw = GetScreenWidth(); + int sh = GetScreenHeight(); + int my_id = suic_net_get_my_id(); + float view_yaw = ((((((*game_state))).yaw)) + (((((*game_state))).recoil_yaw))); + float view_pitch = ((((((*game_state))).pitch)) + (((((*game_state))).recoil_pitch))); + float clamped_view_pitch = view_pitch; + if ((((clamped_view_pitch)) < ((((-1.500000)))))) { + clamped_view_pitch = ((-1.500000)); + } + if ((((clamped_view_pitch)) > ((1.500000)))) { + clamped_view_pitch = 1.500000; + } + struct SuicVec3 forward = suic_v3((((sinf(view_yaw))) * ((cosf(clamped_view_pitch)))), sinf(clamped_view_pitch), (((cosf(view_yaw))) * ((cosf(clamped_view_pitch))))); + float fov = 75.000000; + if ((((*game_state))).scoped) { + fov = 30.000000; + } + suic_begin_mode3d(((((*game_state))).cam_pos).x, ((((*game_state))).cam_pos).y, ((((*game_state))).cam_pos).z, (((((((*game_state))).cam_pos).x)) + (((forward).x))), (((((((*game_state))).cam_pos).y)) + (((forward).y))), (((((((*game_state))).cam_pos).z)) + (((forward).z))), 0.000000, 1.000000, 0.000000, fov, 0); + suic_terrain_draw((((*game_state))).cam_pos); + float border_size = (((SUIC_TERRAIN_MAX)) - ((SUIC_TERRAIN_MIN))); + suic_draw_cube(0.000000, 0.000000, 0.000000, border_size, 1000.000000, border_size, 255, 0, 0, 100); + int i = 0; + while ((((i)) < ((SUIC_NET_MAX_ITEMS)))) { + if (suic_net_item_present(i)) { + int ic_r = 220; + int ic_g = 220; + int ic_b = 220; + int item_type = suic_net_item_type(i); + if ((((item_type)) == ((SUIC_ITEM_MEDKIT)))) { + ic_r = 120; + ic_g = 255; + ic_b = 120; + } + if ((((item_type)) == ((SUIC_ITEM_AMMO_PISTOL)))) { + ic_r = 255; + ic_g = 220; + ic_b = 120; + } + if ((((item_type)) == ((SUIC_ITEM_AMMO_RIFLE)))) { + ic_r = 255; + ic_g = 180; + ic_b = 120; + } + struct SuicVec3 item_pos = suic_v3(suic_net_item_x(i), suic_net_item_y(i), suic_net_item_z(i)); + struct SuicVec3 item_normal = suic_v3(0.000000, 1.000000, 0.000000); + struct SuicColor base_color = ((struct SuicColor){ .r = (uint8_t) ic_r, .g = (uint8_t) ic_g, .b = (uint8_t) ic_b, .a = 255 }); + struct SuicColor lit_color = suic_light_apply_shadows(base_color, item_normal, item_pos, ((&(((*game_state))).light))); + suic_draw_sphere((item_pos).x, (item_pos).y, (item_pos).z, 0.300000, (int) (lit_color).r, (int) (lit_color).g, (int) (lit_color).b, (int) (lit_color).a); + } + i = (((i)) + ((1))); + } + i = 0; + while ((((i)) < ((SUIC_NET_MAX_PLAYERS)))) { + if (suic_net_player_present(i)) { + float interp_factor = ((((((*game_state))).interp_timer)) / (((((1.000000)) / ((20.000000)))))); + if ((((interp_factor)) > ((1.000000)))) { + interp_factor = 1.000000; + } + struct SuicVec3 prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i)); + struct SuicVec3 curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i)); + struct SuicVec3 p = suic_v3_add(suic_v3_mul(prev, (((1.000000)) - ((interp_factor)))), suic_v3_mul(curr, interp_factor)); + int c_r = 255; + int c_g = 80; + int c_b = 80; + if ((((i)) == ((my_id)))) { + c_r = 80; + c_g = 180; + c_b = 255; + } + if (((!suic_net_player_alive(i)))) { + c_r = 120; + c_g = 120; + c_b = 120; + } + struct SuicVec3 player_normal = suic_v3(0.000000, 1.000000, 0.000000); + struct SuicColor base_color = ((struct SuicColor){ .r = (uint8_t) c_r, .g = (uint8_t) c_g, .b = (uint8_t) c_b, .a = 255 }); + struct SuicColor lit_color = suic_light_apply_shadows(base_color, player_normal, p, ((&(((*game_state))).light))); + suic_draw_capsule((p).x, ((((p).y)) - ((0.500000))), (p).z, (p).x, ((((p).y)) + ((0.500000))), (p).z, 0.350000, 8, 8, (int) (lit_color).r, (int) (lit_color).g, (int) (lit_color).b, (int) (lit_color).a); + } + i = (((i)) + ((1))); + } + suic_end_mode3d(); + i = 0; + while ((((i)) < ((SUIC_NET_MAX_PLAYERS)))) { + if (suic_net_player_present(i)) { + float interp_factor = ((((((*game_state))).interp_timer)) / (((((1.000000)) / ((20.000000)))))); + if ((((interp_factor)) > ((1.000000)))) { + interp_factor = 1.000000; + } + struct SuicVec3 prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i)); + struct SuicVec3 curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i)); + struct SuicVec3 p = suic_v3_add(suic_v3_mul(prev, (((1.000000)) - ((interp_factor)))), suic_v3_mul(curr, interp_factor)); + suic_ui_draw_nameplate(sw, sh, (p).x, (p).y, (p).z, suic_net_player_username(i), (((i)) == ((my_id))), ((((*game_state))).cam_pos).x, ((((*game_state))).cam_pos).y, ((((*game_state))).cam_pos).z, (((((((*game_state))).cam_pos).x)) + (((forward).x))), (((((((*game_state))).cam_pos).y)) + (((forward).y))), (((((((*game_state))).cam_pos).z)) + (((forward).z)))); + } + i = (((i)) + ((1))); + } + if (((((((my_id)) == ((255))))) || ((((!suic_net_player_present(my_id))))))) { + suic_draw_text(suic_alloc_array(NULL, sizeof(char), 8, "Offline"), 10, 10, 20, 255, 255, 255, 255); + } + if (((((((my_id)) != ((255))))) && ((suic_net_player_present(my_id))))) { + int weapon = suic_net_player_weapon(my_id); + int mag = suic_net_player_rifle_mag(my_id); + int reserve = suic_net_player_rifle_ammo(my_id); + if ((((weapon)) == ((SUIC_WEAPON_PISTOL)))) { + mag = suic_net_player_pistol_mag(my_id); + reserve = suic_net_player_pistol_ammo(my_id); + } + suic_ui_draw_hud(suic_net_player_hp(my_id), weapon, mag, reserve, suic_net_player_medkits(my_id), suic_net_player_reload_time(my_id)); + } + suic_draw_fps((((sw)) - ((90))), 10); + suic_ui_draw_room_state(sw, sh, suic_net_room_state(), suic_net_countdown(), suic_net_winner_name()); + suic_ui_draw_crosshair(sw, sh, (((*game_state))).cross_spread, (((*game_state))).scoped); +} + +int suic_main(void) { + struct SuicVec3 zero_vec = ((struct SuicVec3){ .x = 0.000000, .y = 0.000000, .z = 0.000000 }); + struct SuicDirectionalLight default_light = ((struct SuicDirectionalLight){ .direction = zero_vec, .color = zero_vec, .intensity = 0.000000, .ambientIntensity = 0.000000, .shadowBias = 0.000000, .shadowIntensity = 0.000000 }); + struct GameState game_state = ((struct GameState){ .cam_pos = ((struct SuicVec3){ .x = 0.000000, .y = 5.000000, .z = 6.000000 }), .yaw = 0.000000, .pitch = 0.000000, .prev_body_pos = ((struct SuicVec3){ .x = 0.000000, .y = 5.000000, .z = 6.000000 }), .current_target_body = ((struct SuicVec3){ .x = 0.000000, .y = 5.000000, .z = 6.000000 }), .interp_timer = 0.000000, .recoil_yaw = 0.000000, .recoil_pitch = 0.000000, .cross_spread = 0.000000, .scoped = false, .fire_cooldown = 0.000000, .shots_in_burst = 0, .burst_reset_timer = 0.000000, .client_tick = 0, .light = default_light }); + int screen_w = 1280; + int screen_h = 720; + suic_init_window(screen_w, screen_h, suic_alloc_array(NULL, sizeof(char), 21, "Voxel Shooter Client")); + suic_set_target_fps(120); + suic_disable_cursor(); + (game_state).light = suic_light_create_default(); + char* username = suic_alloc_array(NULL, sizeof(char), 1, ""); + suic_ui_username_prompt(username, SUIC_NET_USERNAME_MAX); + suic_game_init(((&game_state)), username, suic_alloc_array(NULL, sizeof(char), 10, "127.0.0.1"), 27015); + while ((((suic_window_should_close())) == ((false)))) { + float dt = suic_get_frame_time(); + suic_game_handle_input(((&game_state)), dt); + suic_game_update(((&game_state)), dt); + suic_begin_drawing(); + suic_clear_background(135, 206, 235, 255); + suic_game_render(((&game_state))); + suic_end_drawing(); + } + suic_game_shutdown(); + suic_close_window(); + return 0; +} + +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/game/client.o b/game/client.o new file mode 100755 index 0000000000000000000000000000000000000000..6d7949b2f1b7f96231744fe5694a82cce3746fad GIT binary patch literal 115344 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 () do + suic_terrain_init() + suic_net_init(server_ip, port) + suic_net_send_hello(username) + disable_cursor() +end + +fn suic_game_shutdown() -> () do + suic_terrain_cleanup() + suic_net_shutdown() +end + +fn suic_game_handle_input(game_state: *GameState, dt: float) -> () do + game_state.client_tick = game_state.client_tick + 1 + + # Cooldowns + game_state.fire_cooldown = game_state.fire_cooldown - dt + if game_state.fire_cooldown < 0.0 do + game_state.fire_cooldown = 0.0 + end + game_state.burst_reset_timer = game_state.burst_reset_timer - dt + if game_state.burst_reset_timer <= 0.0 do + game_state.shots_in_burst = 0 + end + + # Recoil recovery + let k = 1.0 - expf(-RECOIL_RETURN * dt) + game_state.recoil_yaw = game_state.recoil_yaw + (0.0 - game_state.recoil_yaw) * k + game_state.recoil_pitch = game_state.recoil_pitch + (0.0 - game_state.recoil_pitch) * k + + let k2 = 1.0 - expf(-CROSS_RETURN * dt) + game_state.cross_spread = game_state.cross_spread + (0.0 - game_state.cross_spread) * k2 + if game_state.cross_spread < 0.01 do + game_state.cross_spread = 0.0 + end + + # Mouse look + let md = get_mouse_delta() + let sens = 0.0025 + game_state.yaw = game_state.yaw - md.x * sens + game_state.pitch = game_state.pitch - md.y * sens + if game_state.pitch < -1.5 do + game_state.pitch = -1.5 + end + if game_state.pitch > 1.5 do + game_state.pitch = 1.5 + end + + # Movement input + let mut move_x = 0.0 + let mut move_z = 0.0 + if is_key_down(65) do # KEY_A + move_x = move_x + 1.0 + end + if is_key_down(68) do # KEY_D + move_x = move_x - 1.0 + end + if is_key_down(87) do # KEY_W + move_z = move_z + 1.0 + end + if is_key_down(83) do # KEY_S + move_z = move_z - 1.0 + end + + # Buttons + let mut buttons = 0 + if is_key_pressed(82) do # KEY_R + buttons = buttons bitor SUIC_BTN_RELOAD + end + if is_key_pressed(49) do # KEY_ONE + buttons = buttons bitor SUIC_BTN_SWITCH_PISTOL + end + if is_key_pressed(50) do # KEY_TWO + buttons = buttons bitor SUIC_BTN_SWITCH_RIFLE + end + if is_key_pressed(70) do # KEY_F + buttons = buttons bitor SUIC_BTN_PICK + end + if is_key_pressed(72) do # KEY_H + buttons = buttons bitor SUIC_BTN_USE_MEDKIT + end + if is_key_pressed(32) do # KEY_SPACE + buttons = buttons bitor SUIC_BTN_JUMP + end + if is_key_pressed(90) do # KEY_Z + game_state.scoped = not game_state.scoped + end + + let my_id = suic_net_get_my_id() + let view_yaw = game_state.yaw + game_state.recoil_yaw + let view_pitch = game_state.pitch + game_state.recoil_pitch + let mut clamped_view_pitch = view_pitch + if clamped_view_pitch < -1.5 do + clamped_view_pitch = -1.5 + end + if clamped_view_pitch > 1.5 do + clamped_view_pitch = 1.5 + end + + if my_id != 255 do + suic_net_send_input(my_id, game_state.client_tick, move_x, move_z, view_yaw, clamped_view_pitch, buttons) + end + + # Shooting + if my_id != 255 and is_mouse_button_down(0) and game_state.fire_cooldown <= 0.0 and suic_net_player_present(my_id) do + let wpn = suic_net_player_weapon(my_id) + let mut rate = RIFLE_FIRE_RATE + if wpn == SUIC_WEAPON_PISTOL do + rate = PISTOL_FIRE_RATE + end + game_state.fire_cooldown = 1.0 / rate + + game_state.shots_in_burst = game_state.shots_in_burst + 1 + game_state.burst_reset_timer = BURST_RESET_TIME + + if wpn == SUIC_WEAPON_PISTOL do + game_state.recoil_pitch = game_state.recoil_pitch + PISTOL_KICK_PITCH + game_state.recoil_yaw = game_state.recoil_yaw + (get_random_value(-1000, 1000) as float / 1000.0) * PISTOL_KICK_YAW + game_state.cross_spread = game_state.cross_spread + PISTOL_CROSS_KICK + game_state.shots_in_burst as float * PISTOL_SPRAY_GROW + end + if wpn == SUIC_WEAPON_RIFLE do + game_state.recoil_pitch = game_state.recoil_pitch + RIFLE_KICK_PITCH + game_state.recoil_yaw = game_state.recoil_yaw + (get_random_value(-1000, 1000) as float / 1000.0) * RIFLE_KICK_YAW + game_state.cross_spread = game_state.cross_spread + RIFLE_CROSS_KICK + game_state.shots_in_burst as float * RIFLE_SPRAY_GROW + end + + suic_net_send_shoot(my_id, game_state.client_tick) + end +end + +fn suic_game_update(game_state: *GameState, dt: float) -> () do + suic_net_poll() + + let my_id = suic_net_get_my_id() + if my_id != 255 and suic_net_player_present(my_id) do + game_state.prev_body_pos = game_state.current_target_body + game_state.current_target_body = suic_v3(suic_net_player_x(my_id), + suic_net_player_y(my_id), + suic_net_player_z(my_id)) + game_state.interp_timer = 0.0 + end + + # Interpolate camera + if my_id != 255 and suic_net_player_present(my_id) do + let mut interp_factor = game_state.interp_timer / (1.0 / 20.0) + if interp_factor > 1.0 do + interp_factor = 1.0 + end + let interp_body = suic_v3_add(suic_v3_mul(game_state.prev_body_pos, 1.0 - interp_factor), + suic_v3_mul(game_state.current_target_body, interp_factor)) + game_state.cam_pos.x = interp_body.x + game_state.cam_pos.y = interp_body.y + 1.0 + game_state.cam_pos.z = interp_body.z + 0.0001 + + let terrain_h = suic_terrain_height(game_state.cam_pos.x, game_state.cam_pos.z) + game_state.cam_pos.y = fmaxf(game_state.cam_pos.y, terrain_h + 1.5) + end + game_state.interp_timer = game_state.interp_timer + dt + + # Forward offset + if my_id != 255 do + let view_yaw = game_state.yaw + game_state.recoil_yaw + let view_pitch = game_state.pitch + game_state.recoil_pitch + let forward = suic_v3(sinf(view_yaw) * cosf(view_pitch), sinf(view_pitch), + cosf(view_yaw) * cosf(view_pitch)) + game_state.cam_pos = suic_v3_add(game_state.cam_pos, suic_v3_mul(forward, 0.3)) + game_state.cam_pos.y = game_state.cam_pos.y - 0.2 + end + + let terrain_h = suic_terrain_height(game_state.cam_pos.x, game_state.cam_pos.z) + game_state.cam_pos.y = fmaxf(game_state.cam_pos.y, terrain_h + 1.5) +end + +fn suic_game_render(game_state: *GameState) -> () do + let sw = get_screen_width() + let sh = get_screen_height() + let my_id = suic_net_get_my_id() + + let view_yaw = game_state.yaw + game_state.recoil_yaw + let view_pitch = game_state.pitch + game_state.recoil_pitch + let mut clamped_view_pitch = view_pitch + if clamped_view_pitch < -1.5 do + clamped_view_pitch = -1.5 + end + if clamped_view_pitch > 1.5 do + clamped_view_pitch = 1.5 + end + + let forward = suic_v3(sinf(view_yaw) * cosf(clamped_view_pitch), sinf(clamped_view_pitch), + cosf(view_yaw) * cosf(clamped_view_pitch)) + + # Camera setup (simplified - need to implement Camera3D struct) + let mut fov = 75.0 + if game_state.scoped do + fov = 30.0 + end + begin_mode3d(game_state.cam_pos.x, game_state.cam_pos.y, game_state.cam_pos.z, + game_state.cam_pos.x + forward.x, game_state.cam_pos.y + forward.y, game_state.cam_pos.z + forward.z, + 0.0, 1.0, 0.0, fov, 0) + + # Terrain + suic_terrain_draw(game_state.cam_pos) + + # Border + let border_size = SUIC_TERRAIN_MAX - SUIC_TERRAIN_MIN + draw_cube(0.0, 0.0, 0.0, border_size, 1000.0, border_size, 255, 0, 0, 100) + + # Items + let mut i = 0 + while i < SUIC_NET_MAX_ITEMS do + if suic_net_item_present(i) do + let mut ic_r = 220 + let mut ic_g = 220 + let mut ic_b = 220 + let item_type = suic_net_item_type(i) + if item_type == SUIC_ITEM_MEDKIT do + ic_r = 120 + ic_g = 255 + ic_b = 120 + end + if item_type == SUIC_ITEM_AMMO_PISTOL do + ic_r = 255 + ic_g = 220 + ic_b = 120 + end + if item_type == SUIC_ITEM_AMMO_RIFLE do + ic_r = 255 + ic_g = 180 + ic_b = 120 + end + + let item_pos = suic_v3(suic_net_item_x(i), suic_net_item_y(i), suic_net_item_z(i)) + let item_normal = suic_v3(0.0, 1.0, 0.0) + let base_color = SuicColor { r: ic_r as u8, g: ic_g as u8, b: ic_b as u8, a: 255 } + let lit_color = suic_light_apply_shadows(base_color, item_normal, item_pos, &game_state.light) + draw_sphere(item_pos.x, item_pos.y, item_pos.z, 0.3, lit_color.r as int, lit_color.g as int, lit_color.b as int, lit_color.a as int) + end + i = i + 1 + end + + # Players + i = 0 + while i < SUIC_NET_MAX_PLAYERS do + if suic_net_player_present(i) do + let mut interp_factor = game_state.interp_timer / (1.0 / 20.0) + if interp_factor > 1.0 do + interp_factor = 1.0 + end + let prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i)) + let curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i)) + let p = suic_v3_add(suic_v3_mul(prev, 1.0 - interp_factor), suic_v3_mul(curr, interp_factor)) + + let mut c_r = 255 + let mut c_g = 80 + let mut c_b = 80 + if i == my_id do + c_r = 80 + c_g = 180 + c_b = 255 + end + if not suic_net_player_alive(i) do + c_r = 120 + c_g = 120 + c_b = 120 + end + + let player_normal = suic_v3(0.0, 1.0, 0.0) + let base_color = SuicColor { r: c_r as u8, g: c_g as u8, b: c_b as u8, a: 255 } + let lit_color = suic_light_apply_shadows(base_color, player_normal, p, &game_state.light) + draw_capsule(p.x, p.y - 0.5, p.z, p.x, p.y + 0.5, p.z, 0.35, 8, 8, lit_color.r as int, lit_color.g as int, lit_color.b as int, lit_color.a as int) + end + i = i + 1 + end + + end_mode3d() + + # Nameplates + i = 0 + while i < SUIC_NET_MAX_PLAYERS do + if suic_net_player_present(i) do + let mut interp_factor = game_state.interp_timer / (1.0 / 20.0) + if interp_factor > 1.0 do + interp_factor = 1.0 + end + let prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i)) + let curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i)) + let p = suic_v3_add(suic_v3_mul(prev, 1.0 - interp_factor), suic_v3_mul(curr, interp_factor)) + + suic_ui_draw_nameplate(sw, sh, p.x, p.y, p.z, suic_net_player_username(i), i == my_id, + game_state.cam_pos.x, game_state.cam_pos.y, game_state.cam_pos.z, + game_state.cam_pos.x + forward.x, game_state.cam_pos.y + forward.y, game_state.cam_pos.z + forward.z) + end + i = i + 1 + end + + # HUD + if my_id == 255 or not suic_net_player_present(my_id) do + draw_text("Offline", 10, 10, 20, 255, 255, 255, 255) + end + if my_id != 255 and suic_net_player_present(my_id) do + let weapon = suic_net_player_weapon(my_id) + let mut mag = suic_net_player_rifle_mag(my_id) + let mut reserve = suic_net_player_rifle_ammo(my_id) + if weapon == SUIC_WEAPON_PISTOL do + mag = suic_net_player_pistol_mag(my_id) + reserve = suic_net_player_pistol_ammo(my_id) + end + suic_ui_draw_hud(suic_net_player_hp(my_id), weapon, mag, reserve, suic_net_player_medkits(my_id), suic_net_player_reload_time(my_id)) + end + + draw_fps(sw - 90, 10) + + # Room state + suic_ui_draw_room_state(sw, sh, suic_net_room_state(), suic_net_countdown(), suic_net_winner_name()) + + # Crosshair + suic_ui_draw_crosshair(sw, sh, game_state.cross_spread, game_state.scoped) +end + fn main() -> int do - init_window(1280, 720, "Voxel Shooter Client") + let zero_vec = SuicVec3 { x: 0.0, y: 0.0, z: 0.0 } + let default_light = SuicDirectionalLight { direction: zero_vec, color: zero_vec, intensity: 0.0, ambientIntensity: 0.0, shadowBias: 0.0, shadowIntensity: 0.0 } + # Game state + let mut game_state = GameState { + cam_pos: SuicVec3 { x: 0.0, y: 5.0, z: 6.0 }, + yaw: 0.0, + pitch: 0.0, + prev_body_pos: SuicVec3 { x: 0.0, y: 5.0, z: 6.0 }, + current_target_body: SuicVec3 { x: 0.0, y: 5.0, z: 6.0 }, + interp_timer: 0.0, + recoil_yaw: 0.0, + recoil_pitch: 0.0, + cross_spread: 0.0, + scoped: false, + fire_cooldown: 0.0, + shots_in_burst: 0, + burst_reset_timer: 0.0, + client_tick: 0, + light: default_light + } + + let screen_w = 1280 + let screen_h = 720 + + init_window(screen_w, screen_h, "Voxel Shooter Client") defer close_window() set_target_fps(120) disable_cursor() + game_state.light = suic_light_create_default() + + # Get username + let mut username = "" + suic_ui_username_prompt(username, SUIC_NET_USERNAME_MAX) + + # Initialize game subsystems + suic_game_init(&game_state, username, "127.0.0.1", 27015) + # Main loop while window_should_close() == false do let dt = get_frame_time() - # Rendering + suic_game_handle_input(&game_state, dt) + suic_game_update(&game_state, dt) + begin_drawing() clear_background(135, 206, 235, 255) - # Draw FPS - draw_fps(10, 10) + suic_game_render(&game_state) end_drawing() end + suic_game_shutdown() + 0 -end \ No newline at end of file +end diff --git a/game/inc/suic_app.c b/game/inc/suic_app.c index c024e0c..1ef6eaf 100644 --- a/game/inc/suic_app.c +++ b/game/inc/suic_app.c @@ -1,6 +1,6 @@ #include "suic_app.h" -#include "../libsuicmez/libsuicmez.h" -#include "runtime.h" +#include "../../libsuicmez/libsuicmez.h" +// #include "runtime.h" // Not available void suic_app_init(int width, int height, const char* title) { suic_init_window(width, height, (char*)title); @@ -18,7 +18,7 @@ void suic_app_frame_begin(void) { } void suic_app_frame_end(void) { - draw_fps(10, 40); + suic_draw_fps(10, 40); suic_end_drawing(); } diff --git a/game/inc/suic_gameplay.c b/game/inc/suic_gameplay.c index 9ec4781..a9caf23 100644 --- a/game/inc/suic_gameplay.c +++ b/game/inc/suic_gameplay.c @@ -1,5 +1,5 @@ #include "suic_app.h" -#include "runtime.h" +// #include "runtime.h" // Not available #include const char* suic_item_name(uint8_t t) { diff --git a/game/inc/suic_terrain.c b/game/inc/suic_terrain.c index 4590576..ab48a2f 100644 --- a/game/inc/suic_terrain.c +++ b/game/inc/suic_terrain.c @@ -245,7 +245,7 @@ void suic_terrain_cleanup(void) { SuicTerrainShader suic_terrain_shader_load(void) { SuicTerrainShader ts = {0}; - ts.shader = LoadShader("terrain.vs", "terrain.fs"); + ts.shader = LoadShader("game/terrain.vs", "game/terrain.fs"); if (ts.shader.id != 0) { ts.locViewPos = GetShaderLocation(ts.shader, "viewPos"); diff --git a/game/junk b/game/junk new file mode 100644 index 0000000..d022b0d --- /dev/null +++ b/game/junk @@ -0,0 +1,457 @@ +# Game Client in Suicmez +# Full rewrite of the C client using Suicmez language + +struct SuicVec3 + x: float, + y: float, + z: float, +end + +struct SuicColor + r: u8, + g: u8, + b: u8, + a: u8, +end + +struct SuicDirectionalLight + direction: SuicVec3, + color: SuicVec3, + intensity: float, + ambientIntensity: float, + shadowBias: float, + shadowIntensity: float, +end + +# Constants +const PISTOL_FIRE_RATE = 4.0 +const RIFLE_FIRE_RATE = 12.0 +const RECOIL_RETURN = 18.0 +const CROSS_RETURN = 14.0 +const PISTOL_KICK_PITCH = 0.010 +const PISTOL_KICK_YAW = 0.004 +const RIFLE_KICK_PITCH = 0.018 +const RIFLE_KICK_YAW = 0.010 +const PISTOL_CROSS_KICK = 2.0 +const RIFLE_CROSS_KICK = 4.0 +const PISTOL_SPRAY_GROW = 0.4 +const RIFLE_SPRAY_GROW = 1.2 +const BURST_RESET_TIME = 0.18 + +const SUIC_BTN_RELOAD = 1 +const SUIC_BTN_SWITCH_PISTOL = 2 +const SUIC_BTN_SWITCH_RIFLE = 4 +const SUIC_BTN_PICK = 8 +const SUIC_BTN_USE_MEDKIT = 16 +const SUIC_BTN_JUMP = 32 + +const SUIC_WEAPON_PISTOL = 0 +const SUIC_WEAPON_RIFLE = 1 + +const SUIC_ITEM_MEDKIT = 1 +const SUIC_ITEM_AMMO_PISTOL = 2 +const SUIC_ITEM_AMMO_RIFLE = 3 + +const SUIC_TERRAIN_SIZE = 256.0 +const SUIC_TERRAIN_SCALE = 1.0 +const SUIC_TERRAIN_MIN = -256.0 * 1.0 / 2.0 +const SUIC_TERRAIN_MAX = 256.0 * 1.0 / 2.0 + +const SUIC_NET_MAX_PLAYERS = 16 +const SUIC_NET_USERNAME_MAX = 16 +const SUIC_NET_MAX_ITEMS = 64 + +struct GameState + cam_pos: SuicVec3, + yaw: float, + pitch: float, + prev_body_pos: SuicVec3, + current_target_body: SuicVec3, + interp_timer: float, + recoil_yaw: float, + recoil_pitch: float, + cross_spread: float, + scoped: bool, + fire_cooldown: float, + shots_in_burst: int, + burst_reset_timer: float, + client_tick: int, + light: SuicDirectionalLight, +end + +fn suic_game_init(game_state: *GameState, username: string, server_ip: string, port: int) -> () do + suic_terrain_init() + suic_net_init(server_ip, port) + suic_net_send_hello(username) + disable_cursor() +end + +fn suic_game_shutdown() -> () do + suic_terrain_cleanup() + suic_net_shutdown() +end + +fn suic_game_handle_input(game_state: *GameState, dt: float) -> () do + game_state.client_tick = game_state.client_tick + 1 + + # Cooldowns + game_state.fire_cooldown = game_state.fire_cooldown - dt + if game_state.fire_cooldown < 0.0 do + game_state.fire_cooldown = 0.0 + end + game_state.burst_reset_timer = game_state.burst_reset_timer - dt + if game_state.burst_reset_timer <= 0.0 do + game_state.shots_in_burst = 0 + end + + # Recoil recovery + let k = 1.0 - expf(-RECOIL_RETURN * dt) + game_state.recoil_yaw = game_state.recoil_yaw + (0.0 - game_state.recoil_yaw) * k + game_state.recoil_pitch = game_state.recoil_pitch + (0.0 - game_state.recoil_pitch) * k + + let k2 = 1.0 - expf(-CROSS_RETURN * dt) + game_state.cross_spread = game_state.cross_spread + (0.0 - game_state.cross_spread) * k2 + if game_state.cross_spread < 0.01 do + game_state.cross_spread = 0.0 + end + + # Mouse look + let md = get_mouse_delta() + let sens = 0.0025 + game_state.yaw = game_state.yaw - md.x * sens + game_state.pitch = game_state.pitch - md.y * sens + if game_state.pitch < -1.5 do + game_state.pitch = -1.5 + end + if game_state.pitch > 1.5 do + game_state.pitch = 1.5 + end + + # Movement input + let mut move_x = 0.0 + let mut move_z = 0.0 + if is_key_down(65) do # KEY_A + move_x = move_x + 1.0 + end + if is_key_down(68) do # KEY_D + move_x = move_x - 1.0 + end + if is_key_down(87) do # KEY_W + move_z = move_z + 1.0 + end + if is_key_down(83) do # KEY_S + move_z = move_z - 1.0 + end + + # Buttons + let mut buttons = 0 + if is_key_pressed(82) do # KEY_R + buttons = buttons bitor SUIC_BTN_RELOAD + end + if is_key_pressed(49) do # KEY_ONE + buttons = buttons bitor SUIC_BTN_SWITCH_PISTOL + end + if is_key_pressed(50) do # KEY_TWO + buttons = buttons bitor SUIC_BTN_SWITCH_RIFLE + end + if is_key_pressed(70) do # KEY_F + buttons = buttons bitor SUIC_BTN_PICK + end + if is_key_pressed(72) do # KEY_H + buttons = buttons bitor SUIC_BTN_USE_MEDKIT + end + if is_key_pressed(32) do # KEY_SPACE + buttons = buttons bitor SUIC_BTN_JUMP + end + if is_key_pressed(90) do # KEY_Z + game_state.scoped = not game_state.scoped + end + + let my_id = suic_net_get_my_id() + let view_yaw = game_state.yaw + game_state.recoil_yaw + let view_pitch = game_state.pitch + game_state.recoil_pitch + let mut clamped_view_pitch = view_pitch + if clamped_view_pitch < -1.5 do + clamped_view_pitch = -1.5 + end + if clamped_view_pitch > 1.5 do + clamped_view_pitch = 1.5 + end + + if my_id != 255 do + suic_net_send_input(my_id, game_state.client_tick, move_x, move_z, view_yaw, clamped_view_pitch, buttons) + end + + # Shooting + if my_id != 255 and is_mouse_button_down(0) and game_state.fire_cooldown <= 0.0 and suic_net_player_present(my_id) do + let wpn = suic_net_player_weapon(my_id) + let mut rate = RIFLE_FIRE_RATE + if wpn == SUIC_WEAPON_PISTOL do + rate = PISTOL_FIRE_RATE + end + game_state.fire_cooldown = 1.0 / rate + + game_state.shots_in_burst = game_state.shots_in_burst + 1 + game_state.burst_reset_timer = BURST_RESET_TIME + + if wpn == SUIC_WEAPON_PISTOL do + game_state.recoil_pitch = game_state.recoil_pitch + PISTOL_KICK_PITCH + game_state.recoil_yaw = game_state.recoil_yaw + (get_random_value(-1000, 1000) as float / 1000.0) * PISTOL_KICK_YAW + game_state.cross_spread = game_state.cross_spread + PISTOL_CROSS_KICK + game_state.shots_in_burst as float * PISTOL_SPRAY_GROW + end + if wpn == SUIC_WEAPON_RIFLE do + game_state.recoil_pitch = game_state.recoil_pitch + RIFLE_KICK_PITCH + game_state.recoil_yaw = game_state.recoil_yaw + (get_random_value(-1000, 1000) as float / 1000.0) * RIFLE_KICK_YAW + game_state.cross_spread = game_state.cross_spread + RIFLE_CROSS_KICK + game_state.shots_in_burst as float * RIFLE_SPRAY_GROW + end + + suic_net_send_shoot(my_id, game_state.client_tick) + end +end + +fn suic_game_update(game_state: *GameState, dt: float) -> () do + suic_net_poll() + + let my_id = suic_net_get_my_id() + if my_id != 255 and suic_net_player_present(my_id) do + game_state.prev_body_pos = game_state.current_target_body + game_state.current_target_body = suic_v3(suic_net_player_x(my_id), + suic_net_player_y(my_id), + suic_net_player_z(my_id)) + game_state.interp_timer = 0.0 + end + + # Interpolate camera + if my_id != 255 and suic_net_player_present(my_id) do + let mut interp_factor = game_state.interp_timer / (1.0 / 20.0) + if interp_factor > 1.0 do + interp_factor = 1.0 + end + let interp_body = suic_v3_add(suic_v3_mul(game_state.prev_body_pos, 1.0 - interp_factor), + suic_v3_mul(game_state.current_target_body, interp_factor)) + game_state.cam_pos.x = interp_body.x + game_state.cam_pos.y = interp_body.y + 1.0 + game_state.cam_pos.z = interp_body.z + 0.0001 + + let terrain_h = suic_terrain_height(game_state.cam_pos.x, game_state.cam_pos.z) + game_state.cam_pos.y = fmaxf(game_state.cam_pos.y, terrain_h + 1.5) + end + game_state.interp_timer = game_state.interp_timer + dt + + # Forward offset + if my_id != 255 do + let view_yaw = game_state.yaw + game_state.recoil_yaw + let view_pitch = game_state.pitch + game_state.recoil_pitch + let forward = suic_v3(sinf(view_yaw) * cosf(view_pitch), sinf(view_pitch), + cosf(view_yaw) * cosf(view_pitch)) + game_state.cam_pos = suic_v3_add(game_state.cam_pos, suic_v3_mul(forward, 0.3)) + game_state.cam_pos.y = game_state.cam_pos.y - 0.2 + end + + let terrain_h = suic_terrain_height(game_state.cam_pos.x, game_state.cam_pos.z) + game_state.cam_pos.y = fmaxf(game_state.cam_pos.y, terrain_h + 1.5) +end + +fn suic_game_render(game_state: *GameState) -> () do + let sw = get_screen_width() + let sh = get_screen_height() + let my_id = suic_net_get_my_id() + + let view_yaw = game_state.yaw + game_state.recoil_yaw + let view_pitch = game_state.pitch + game_state.recoil_pitch + let mut clamped_view_pitch = view_pitch + if clamped_view_pitch < -1.5 do + clamped_view_pitch = -1.5 + end + if clamped_view_pitch > 1.5 do + clamped_view_pitch = 1.5 + end + + let forward = suic_v3(sinf(view_yaw) * cosf(clamped_view_pitch), sinf(clamped_view_pitch), + cosf(view_yaw) * cosf(clamped_view_pitch)) + + # Camera setup (simplified - need to implement Camera3D struct) + let mut fov = 75.0 + if game_state.scoped do + fov = 30.0 + end + begin_mode3d(game_state.cam_pos.x, game_state.cam_pos.y, game_state.cam_pos.z, + game_state.cam_pos.x + forward.x, game_state.cam_pos.y + forward.y, game_state.cam_pos.z + forward.z, + 0.0, 1.0, 0.0, fov, 0) + + # Terrain + suic_terrain_draw(game_state.cam_pos) + + # Border + let border_size = SUIC_TERRAIN_MAX - SUIC_TERRAIN_MIN + draw_cube(0.0, 0.0, 0.0, border_size, 1000.0, border_size, 255, 0, 0, 100) + + # Items + let mut i = 0 + while i < SUIC_NET_MAX_ITEMS do + if suic_net_item_present(i) do + let mut ic_r = 220 + let mut ic_g = 220 + let mut ic_b = 220 + let item_type = suic_net_item_type(i) + if item_type == SUIC_ITEM_MEDKIT do + ic_r = 120 + ic_g = 255 + ic_b = 120 + end + if item_type == SUIC_ITEM_AMMO_PISTOL do + ic_r = 255 + ic_g = 220 + ic_b = 120 + end + if item_type == SUIC_ITEM_AMMO_RIFLE do + ic_r = 255 + ic_g = 180 + ic_b = 120 + end + + let item_pos = suic_v3(suic_net_item_x(i), suic_net_item_y(i), suic_net_item_z(i)) + let item_normal = suic_v3(0.0, 1.0, 0.0) + let base_color = SuicColor { r: ic_r as u8, g: ic_g as u8, b: ic_b as u8, a: 255 } + let lit_color = suic_light_apply_shadows(base_color, item_normal, item_pos, game_state.light) + draw_sphere(item_pos.x, item_pos.y, item_pos.z, 0.3, lit_color.r as int, lit_color.g as int, lit_color.b as int, lit_color.a as int) + end + i = i + 1 + end + + # Players + i = 0 + while i < SUIC_NET_MAX_PLAYERS do + if suic_net_player_present(i) do + let mut interp_factor = game_state.interp_timer / (1.0 / 20.0) + if interp_factor > 1.0 do + interp_factor = 1.0 + end + let prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i)) + let curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i)) + let p = suic_v3_add(suic_v3_mul(prev, 1.0 - interp_factor), suic_v3_mul(curr, interp_factor)) + + let mut c_r = 255 + let mut c_g = 80 + let mut c_b = 80 + if i == my_id do + c_r = 80 + c_g = 180 + c_b = 255 + end + if not suic_net_player_alive(i) do + c_r = 120 + c_g = 120 + c_b = 120 + end + + let player_normal = suic_v3(0.0, 1.0, 0.0) + let base_color = SuicColor { r: c_r as u8, g: c_g as u8, b: c_b as u8, a: 255 } + let lit_color = suic_light_apply_shadows(base_color, player_normal, p, game_state.light) + draw_capsule(p.x, p.y - 0.5, p.z, p.x, p.y + 0.5, p.z, 0.35, 8, 8, lit_color.r as int, lit_color.g as int, lit_color.b as int, lit_color.a as int) + end + i = i + 1 + end + + end_mode3d() + + # Nameplates + i = 0 + while i < SUIC_NET_MAX_PLAYERS do + if suic_net_player_present(i) do + let mut interp_factor = game_state.interp_timer / (1.0 / 20.0) + if interp_factor > 1.0 do + interp_factor = 1.0 + end + let prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i)) + let curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i)) + let p = suic_v3_add(suic_v3_mul(prev, 1.0 - interp_factor), suic_v3_mul(curr, interp_factor)) + + suic_ui_draw_nameplate(sw, sh, p.x, p.y, p.z, suic_net_player_username(i), i == my_id, + g_cam_pos.x, g_cam_pos.y, g_cam_pos.z, + g_cam_pos.x + forward.x, g_cam_pos.y + forward.y, g_cam_pos.z + forward.z) + i = i + 1 + end + + # HUD + if my_id == 255 or not suic_net_player_present(my_id) do + draw_text("Offline", 10, 10, 20, 255, 255, 255, 255) + end + if my_id != 255 and suic_net_player_present(my_id) do + let weapon = suic_net_player_weapon(my_id) + let mut mag = suic_net_player_rifle_mag(my_id) + let mut reserve = suic_net_player_rifle_ammo(my_id) + if weapon == SUIC_WEAPON_PISTOL do + mag = suic_net_player_pistol_mag(my_id) + reserve = suic_net_player_pistol_ammo(my_id) + end + suic_ui_draw_hud(suic_net_player_hp(my_id), weapon, mag, reserve, suic_net_player_medkits(my_id), suic_net_player_reload_time(my_id)) + end + + draw_fps(sw - 90, 10) + + # Room state + suic_ui_draw_room_state(sw, sh, suic_net_room_state(), suic_net_countdown(), suic_net_winner_name()) + + # Crosshair + suic_ui_draw_crosshair(sw, sh, game_state.cross_spread, game_state.scoped) +end + +fn main() -> int do + let zero_vec = SuicVec3 { x: 0.0, y: 0.0, z: 0.0 } + let default_light = SuicDirectionalLight { direction: zero_vec, color: zero_vec, intensity: 0.0, ambientIntensity: 0.0, shadowBias: 0.0, shadowIntensity: 0.0 } + # Game state + let mut game_state = GameState { + cam_pos: SuicVec3 { x: 0.0, y: 5.0, z: 6.0 }, + yaw: 0.0, + pitch: 0.0, + prev_body_pos: SuicVec3 { x: 0.0, y: 5.0, z: 6.0 }, + current_target_body: SuicVec3 { x: 0.0, y: 5.0, z: 6.0 }, + interp_timer: 0.0, + recoil_yaw: 0.0, + recoil_pitch: 0.0, + cross_spread: 0.0, + scoped: false, + fire_cooldown: 0.0, + shots_in_burst: 0, + burst_reset_timer: 0.0, + client_tick: 0, + light: default_light + } + + let screen_w = 1280 + let screen_h = 720 + + init_window(screen_w, screen_h, "Voxel Shooter Client") + defer close_window() + set_target_fps(120) + disable_cursor() + + game_state.light = suic_light_create_default() + + # Get username + let mut username = "" + suic_ui_username_prompt(&username, SUIC_NET_USERNAME_MAX) + + # Initialize game subsystems + suic_game_init(&game_state, username, "127.0.0.1", 27015) + + # Main loop + while window_should_close() == false do + let dt = get_frame_time() + + suic_game_handle_input(&game_state, dt) + suic_game_update(&game_state, dt) + + begin_drawing() + clear_background(135, 206, 235, 255) + + suic_game_render(&game_state) + + end_drawing() + end + + suic_game_shutdown() + + 0 +end diff --git a/libsuicmez/libsuicmez.c b/libsuicmez/libsuicmez.c index a9f1f9b..8748272 100644 --- a/libsuicmez/libsuicmez.c +++ b/libsuicmez/libsuicmez.c @@ -83,6 +83,10 @@ void suic_set_target_fps(int fps) { SetTargetFPS(fps); } void suic_enable_msaa_4x(void) { SetConfigFlags(FLAG_MSAA_4X_HINT); } +int suic_get_screen_width(void) { return GetScreenWidth(); } + +int suic_get_screen_height(void) { return GetScreenHeight(); } + // Timing float suic_get_frame_time(void) { return GetFrameTime(); } @@ -148,6 +152,14 @@ void suic_draw_cube_wires(float x, float y, float z, float width, float height, DrawCubeWires((Vector3){x, y, z}, width, height, length, (Color){r, g, b, a}); } +void suic_draw_sphere(float x, float y, float z, float radius, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + DrawSphere((Vector3){x, y, z}, radius, (Color){r, g, b, a}); +} + +void suic_draw_capsule(float start_x, float start_y, float start_z, float end_x, float end_y, float end_z, float radius, int slices, int rings, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + DrawCapsule((Vector3){start_x, start_y, start_z}, (Vector3){end_x, end_y, end_z}, radius, slices, rings, (Color){r, g, b, a}); +} + // 2D Drawing (UI Framework Utils) void suic_draw_text(const char *text, int x, int y, int font_size, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { DrawText(text, x, y, font_size, (Color){r, g, b, a}); @@ -173,6 +185,10 @@ void suic_draw_line(int start_x, int start_y, int end_x, int end_y, unsigned cha DrawLine(start_x, start_y, end_x, end_y, (Color){r, g, b, a}); } +void suic_draw_fps(int x, int y) { + DrawFPS(x, y); +} + // Input - Keyboard bool suic_is_key_down(int key) { return IsKeyDown(key); } @@ -205,6 +221,9 @@ float suic_get_mouse_delta_x(void) { return GetMouseDelta().x; } float suic_get_mouse_delta_y(void) { return GetMouseDelta().y; } +// Random number generation +int suic_get_random_value(int min, int max) { return GetRandomValue(min, max); } + // Input - Helper functions for gamedev int suic_is_movement_input(void) { // Returns bitmask: 1=W (forward), 2=A (left), 4=S (backward), 8=D (right) @@ -996,3 +1015,15 @@ int suic_fishsoup_packet_send(int fd, fishsoup_packet_t* packet) { int suic_fishsoup_packet_recv(int fd, fishsoup_packet_t* packet) { return fishsoup_packet_recv(fd, packet); } + +// ============================================================================ +// MATH FUNCTIONS +// ============================================================================ + +float suic_sinf(float x) { return sinf(x); } + +float suic_cosf(float x) { return cosf(x); } + +float suic_expf(float x) { return expf(x); } + +float suic_fmaxf(float a, float b) { return fmaxf(a, b); } diff --git a/libsuicmez/libsuicmez.h b/libsuicmez/libsuicmez.h index 07d6ef9..7e2e7a0 100644 --- a/libsuicmez/libsuicmez.h +++ b/libsuicmez/libsuicmez.h @@ -16,8 +16,12 @@ // ODE support #include -// Perlin noise support -#include "perlin/perlin.h" +// Internal game headers +#include "../game/inc/suic_math.h" +#include "../game/inc/suic_net.h" +#include "../game/inc/suic_ui.h" +#include "../game/inc/suic_terrain.h" +#include "../game/inc/suic_lighting.h" // Logging levels typedef enum suic_log_level { @@ -98,6 +102,8 @@ void suic_close_window(void); bool suic_window_should_close(void); void suic_set_target_fps(int fps); void suic_enable_msaa_4x(void); +int suic_get_screen_width(void); +int suic_get_screen_height(void); // Timing float suic_get_frame_time(void); @@ -127,6 +133,8 @@ void suic_end_mode3d(void); // 3D Drawing void suic_draw_cube(float x, float y, float z, float width, float height, float length, unsigned char r, unsigned char g, unsigned char b, unsigned char a); void suic_draw_cube_wires(float x, float y, float z, float width, float height, float length, unsigned char r, unsigned char g, unsigned char b, unsigned char a); +void suic_draw_sphere(float x, float y, float z, float radius, unsigned char r, unsigned char g, unsigned char b, unsigned char a); +void suic_draw_capsule(float start_x, float start_y, float start_z, float end_x, float end_y, float end_z, float radius, int slices, int rings, unsigned char r, unsigned char g, unsigned char b, unsigned char a); // 2D Drawing (UI Framework Utils) void suic_draw_text(const char *text, int x, int y, int font_size, unsigned char r, unsigned char g, unsigned char b, unsigned char a); @@ -135,6 +143,7 @@ void suic_draw_rectangle_lines(int x, int y, int width, int height, unsigned cha int suic_measure_text(const char *text, int font_size); void suic_draw_circle(int center_x, int center_y, float radius, unsigned char r, unsigned char g, unsigned char b, unsigned char a); void suic_draw_line(int start_x, int start_y, int end_x, int end_y, unsigned char r, unsigned char g, unsigned char b, unsigned char a); +void suic_draw_fps(int x, int y); // Input - Keyboard bool suic_is_key_down(int key); @@ -157,6 +166,9 @@ int suic_is_movement_input(void); // Returns bitmask: 1=W, 2=A, 4=S, 8=D int suic_is_sprint_held(void); int suic_is_jump_pressed(void); +// Random number generation +int suic_get_random_value(int min, int max); + // Cursor management void suic_disable_cursor(void); void suic_enable_cursor(void); @@ -337,6 +349,13 @@ void suic_player_controller_jump(suic_player_controller* player); void suic_player_controller_get_position(suic_player_controller* player, float *x, float *y, float *z); void suic_player_controller_set_position(suic_player_controller* player, float x, float y, float z); +/* ===== MATH FUNCTIONS ===== */ + +float suic_sinf(float x); +float suic_cosf(float x); +float suic_expf(float x); +float suic_fmaxf(float a, float b); + /* ===== FILE I/O API ===== */ typedef void* FileHandle; diff --git a/src/c_lowerer/declaration_transpiler.rs b/src/c_lowerer/declaration_transpiler.rs index 0a987de..577a09d 100644 --- a/src/c_lowerer/declaration_transpiler.rs +++ b/src/c_lowerer/declaration_transpiler.rs @@ -143,7 +143,7 @@ impl DeclarationTranspiler { Some(TypeAnnot::Cons(name, args)) if args.is_empty() => convert_to_c_type(name), Some(TypeAnnot::Cons(name, _args)) => { // Generic types - for now just use the base name - Ok(CType::Ptr(Box::new(CType::Struct(name.clone())))) + Ok(CType::Struct(name.clone())) } Some(TypeAnnot::Ptr(inner)) => { let inner_type = self.type_annot_to_c_type(&Some(*inner.clone()))?; @@ -192,6 +192,8 @@ pub fn convert_to_c_type(name: &String) -> Result { "bool" => Ok(CType::Bool), "string" => Ok(CType::Ptr(Box::new(CType::Char))), "()" => Ok(CType::Void), // Unit type - _ => Ok(CType::Ptr(Box::new(CType::Struct(name.clone())))), // Assume heap-allocated struct + "unit" => Ok(CType::Void), + "Vec2" => Ok(CType::Struct("Vector2".to_string())), + _ => Ok(CType::Struct(name.clone())), // Map to value type by default } } diff --git a/src/c_lowerer/statements_transpiler.rs b/src/c_lowerer/statements_transpiler.rs index be26df7..cf21848 100644 --- a/src/c_lowerer/statements_transpiler.rs +++ b/src/c_lowerer/statements_transpiler.rs @@ -405,14 +405,18 @@ impl StatementsTranspiler { c_stmts.push(defer_stmt); } if let Some(last) = last_stmt { - match &last.kind { - TypedExprKind::Return(_) => { - c_stmts.push(self.transpile_stmt(last)?); - } - _ => { - // Convert to return statement - let c_expr = self.transpile_expr(last)?; - c_stmts.push(CStmt::Return(Some(c_expr))); + if expr.ty == Type::Unit { + c_stmts.push(self.transpile_stmt(last)?); + } else { + match &last.kind { + TypedExprKind::Return(_) => { + c_stmts.push(self.transpile_stmt(last)?); + } + _ => { + // Convert to return statement + let c_expr = self.transpile_expr(last)?; + c_stmts.push(CStmt::Return(Some(c_expr))); + } } } } @@ -506,8 +510,8 @@ impl StatementsTranspiler { 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::Struct(name, _) => Ok(CType::Struct(name.clone())), + Type::Enum(name, _) => Ok(CType::Struct(name.clone())), Type::Tuple(types) => { let mut fields = Vec::new(); for (i, inner_ty) in types.iter().enumerate() { diff --git a/src/codegen/transpiler.rs b/src/codegen/transpiler.rs index ec2a448..50b6d95 100644 --- a/src/codegen/transpiler.rs +++ b/src/codegen/transpiler.rs @@ -74,7 +74,7 @@ impl Transpiler { /// Check if a CType is a pointer type (needs to be tracked in GC bitmap) fn is_pointer_type(ty: &CType) -> bool { - matches!(ty, CType::Ptr(_) | CType::Array(_) | CType::Struct(_)) + matches!(ty, CType::Ptr(_) | CType::Array(_)) } /// Generate pointer bitmap for a struct's fields @@ -272,6 +272,10 @@ impl Transpiler { } } TypedASTNodeKind::Const(c) => { + // Skip constants that are already defined in headers as macros + if matches!(c.name.as_str(), "SUIC_TERRAIN_SIZE" | "SUIC_TERRAIN_SCALE" | "SUIC_TERRAIN_MIN" | "SUIC_TERRAIN_MAX" | "SUIC_NET_MAX_PLAYERS" | "SUIC_NET_USERNAME_MAX" | "SUIC_NET_MAX_ITEMS" | "SUIC_BTN_RELOAD" | "SUIC_BTN_SWITCH_PISTOL" | "SUIC_BTN_SWITCH_RIFLE" | "SUIC_BTN_PICK" | "SUIC_BTN_USE_MEDKIT" | "SUIC_BTN_JUMP" | "SUIC_WEAPON_PISTOL" | "SUIC_WEAPON_RIFLE" | "SUIC_ITEM_MEDKIT" | "SUIC_ITEM_AMMO_PISTOL" | "SUIC_ITEM_AMMO_RIFLE") { + return Ok(()); + } let value_code = self.stmt_transpiler.transpile_expr(&c.value)?; let var_decl = CVarDecl { name: c.name.clone(), @@ -338,7 +342,7 @@ impl Transpiler { fn generate_struct_decl(&self, struct_decl: &CStructDecl) -> String { // Skip predefined structs that are already declared in included headers - if matches!(struct_decl.name.as_str(), "Shader" | "Color") { + if matches!(struct_decl.name.as_str(), "Shader" | "Color" | "SuicVec3" | "SuicColor" | "SuicDirectionalLight") { return String::new(); } let mut output = format!("struct {} {{\n", struct_decl.name); @@ -675,8 +679,9 @@ impl Transpiler { "begin_mode3d" => "suic_begin_mode3d", "end_mode3d" => "suic_end_mode3d", "draw_cube" => "suic_draw_cube", + "draw_sphere" => "suic_draw_sphere", + "draw_capsule" => "suic_draw_capsule", "draw_cube_wires" => "suic_draw_cube_wires", - "enable_msaa_4x" => "suic_enable_msaa_4x", "enable_msaa_8x" => "suic_enable_msaa_8x", "enable_msaa_16x" => "suic_enable_msaa_16x", // Input - Keyboard @@ -687,7 +692,7 @@ impl Transpiler { "is_mouse_button_down" => "suic_is_mouse_button_down", "is_mouse_button_pressed" => "suic_is_mouse_button_pressed", "is_mouse_button_released" => "suic_is_mouse_button_released", - "get_mouse_position" => "suic_get_mouse_position", + "get_mouse_position" => "GetMousePosition", "get_mouse_delta" => "suic_get_mouse_delta", "get_mouse_x" => "suic_get_mouse_x", "get_mouse_y" => "suic_get_mouse_y", @@ -707,13 +712,13 @@ impl Transpiler { "rl_translate_f" => "suic_rl_translate_f", "rl_rotate_f" => "suic_rl_rotate_f", // 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", + "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", + "draw_fps" => "suic_draw_fps", // Player controller functions "player_controller_create" => "suic_player_controller_create", "player_controller_destroy" => "suic_player_controller_destroy", @@ -728,6 +733,24 @@ impl Transpiler { "sin" => "sin", "cos" => "cos", "floor" => "floor", + "get_random_value" => "GetRandomValue", + "get_screen_width" => "GetScreenWidth", + "get_screen_height" => "GetScreenHeight", + "fmaxf" => "fmaxf", + "fminf" => "fminf", + "expf" => "expf", + "sinf" => "sinf", + "cosf" => "cosf", + "tanf" => "tanf", + "asinf" => "asinf", + "acosf" => "acosf", + "atanf" => "atanf", + "atan2f" => "atan2f", + "powf" => "powf", + "sqrtf" => "sqrtf", + "floorf" => "floorf", + "ceilf" => "ceilf", + "roundf" => "roundf", // Perlin noise functions "pnoise1d" => "pnoise1d", "pnoise2d" => "pnoise2d", @@ -771,14 +794,14 @@ impl Transpiler { } CExpr::BinOp(lhs, op, rhs) => { format!( - "({} {} {})", + "((({})) {} (({})))", self.generate_expr(lhs), op.to_string(), self.generate_expr(rhs) ) } CExpr::UnOp(op, expr) => { - format!("{}{}", op.to_string(), self.generate_expr(expr)) + format!("(({}{}))", op.to_string(), self.generate_expr(expr)) } CExpr::Cast(expr, ty) => { match (*expr.clone(), ty.clone()) { @@ -786,9 +809,9 @@ impl Transpiler { _ => format!("({}) {}", ty.to_string(), self.generate_expr(expr)), } } - CExpr::AddrOf(expr) => format!("&{}", self.generate_expr(expr)), - CExpr::Deref(expr) => format!("*{}", self.generate_expr(expr)), - CExpr::Dot(expr, field) => format!("(*{}).{}", self.generate_expr(expr), field), + CExpr::AddrOf(expr) => format!("(&({}))", self.generate_expr(expr)), + CExpr::Deref(expr) => format!("(*({}))", self.generate_expr(expr)), + CExpr::Dot(expr, field) => format!("({}).{}", self.generate_expr(expr), field), CExpr::Index(array, index) => format!( "{}[{}]", self.generate_expr(array), @@ -804,7 +827,7 @@ impl Transpiler { "Vec3" => "suic_vec3".to_string(), _ => format!("struct {}", struct_name), }; - 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 @@ -832,8 +855,8 @@ impl Transpiler { }; format!( - "suic_alloc_struct(&sui_typeinfo_{}, sizeof(struct {}), &(struct {}){{ .discriminant = {}, .data = {{ .{} = {} }} }})", - enum_name, enum_name, enum_name, variant_index, union_field_name, variant_init + "(struct {}){{ .discriminant = {}, .data = {{ .{} = {} }} }}", + enum_name, variant_index, union_field_name, variant_init ) } diff --git a/src/main.rs b/src/main.rs index 85f7886..3b547ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -123,6 +123,7 @@ fn main() { if let Err(e) = run_file(&filename, args.debug) { eprintln!("Error: {}", e); + std::process::exit(1); } } else { eprintln!( diff --git a/src/parser.rs b/src/parser.rs index 79eb17a..3235bd1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -231,8 +231,20 @@ impl Parser { 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), + 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)) { @@ -949,10 +961,14 @@ impl Parser { let size = match self.next() { Some((Token::Int(n), _)) => n, Some((_, span)) => { - return self.error("Expected integer size for fixed array".to_string(), 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); + return self.error( + "Expected integer size for fixed array".to_string(), + start..start, + ); } }; self.expect(Token::RBracket)?; @@ -1699,10 +1715,14 @@ impl Parser { let size = match self.next() { Some((Token::Int(n), _)) => n, Some((_, span)) => { - return self.error("Expected integer size for fixed array".to_string(), 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); + return self.error( + "Expected integer size for fixed array".to_string(), + start..start, + ); } }; self.expect(Token::RBracket)?; @@ -1979,7 +1999,7 @@ impl Parser { } Some(token) => { let span = self.peek_span().unwrap_or(start..start); - self.error(format!("Unexpected token in pattern: {:?}. Expected variable names, struct patterns like Struct {{ field }}, or enum patterns like Enum::Variant", token), span) + self.error(format!("Unexpected token while parsing for expression: {:?}. Expected variable names, struct patterns like Struct {{ field }}, or enum patterns like Enum::Variant", token), span) } None => self.error( "Unexpected end of file in pattern. Expected a complete pattern.".to_string(), diff --git a/src/typechecker.rs b/src/typechecker.rs index 3c22207..60eac16 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -313,6 +313,12 @@ impl TypeEnv { } } + fn update_var_type(&mut self, id: &crate::ast::BindingId, ty: Type) { + if let Some(var_info) = self.vars.get_mut(id) { + var_info.ty = ty; + } + } + fn add_type(&mut self, name: String, info: TypeInfo) { self.types.insert(name, info); } @@ -371,6 +377,16 @@ impl TypeChecker { fn add_builtin_functions(&mut self) { // Builtin types + self.env.types.insert( + "suic_vec2".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( "Vector3".to_string(), TypeInfo { @@ -425,6 +441,43 @@ impl TypeChecker { parameters: vec![], }, ); + self.env.types.insert( + "SuicVec3".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( + "SuicColor".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( + "SuicDirectionalLight".to_string(), + TypeInfo { + kind: TypeInfoKind::Struct(vec![ + ("direction".to_string(), TypeAnnot::Cons("SuicVec3".to_string(), vec![])), + ("color".to_string(), TypeAnnot::Cons("SuicVec3".to_string(), vec![])), + ("intensity".to_string(), TypeAnnot::Var("float".to_string())), + ("ambientIntensity".to_string(), TypeAnnot::Var("float".to_string())), + ("shadowBias".to_string(), TypeAnnot::Var("float".to_string())), + ("shadowIntensity".to_string(), TypeAnnot::Var("float".to_string())), + ]), + parameters: vec![], + }, + ); // Builtin functions // Raylib @@ -497,7 +550,7 @@ impl TypeChecker { FunctionType { type_params: vec![], params: vec![], - return_type: Type::Struct("Vector2".to_string(), vec![]), + return_type: Type::Struct("suic_vec2".to_string(), vec![]), }, ); self.env.functions.insert( @@ -1329,7 +1382,7 @@ impl TypeChecker { FunctionType { type_params: vec![], params: vec![], - return_type: Type::Struct("Vec2".to_string(), vec![]), // suic_vec2 + return_type: Type::Struct("suic_vec2".to_string(), vec![]), // suic_vec2 }, ); self.env.functions.insert( @@ -1369,7 +1422,7 @@ impl TypeChecker { FunctionType { type_params: vec![], params: vec![Type::Int], // mouse button - return_type: Type::Int, + return_type: Type::Bool, }, ); self.env.functions.insert( @@ -1377,7 +1430,7 @@ impl TypeChecker { FunctionType { type_params: vec![], params: vec![Type::Int], // mouse button - return_type: Type::Int, + return_type: Type::Bool, }, ); self.env.functions.insert( @@ -1385,7 +1438,7 @@ impl TypeChecker { FunctionType { type_params: vec![], params: vec![Type::Int], // mouse button - return_type: Type::Int, + return_type: Type::Bool, }, ); @@ -1778,6 +1831,557 @@ impl TypeChecker { return_type: Type::Int, }, ); + + // Math functions + self.env.functions.insert( + "suic_v3".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float, Type::Float], + return_type: Type::Struct("SuicVec3".to_string(), vec![]), + }, + ); + self.env.functions.insert( + "suic_v3_add".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Struct("SuicVec3".to_string(), vec![]), Type::Struct("SuicVec3".to_string(), vec![])], + return_type: Type::Struct("SuicVec3".to_string(), vec![]), + }, + ); + self.env.functions.insert( + "suic_v3_sub".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Struct("SuicVec3".to_string(), vec![]), Type::Struct("SuicVec3".to_string(), vec![])], + return_type: Type::Struct("SuicVec3".to_string(), vec![]), + }, + ); + self.env.functions.insert( + "suic_v3_mul".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Struct("SuicVec3".to_string(), vec![]), Type::Float], + return_type: Type::Struct("SuicVec3".to_string(), vec![]), + }, + ); + self.env.functions.insert( + "suic_v3_dot".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Struct("SuicVec3".to_string(), vec![]), Type::Struct("SuicVec3".to_string(), vec![])], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_v3_len".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Struct("SuicVec3".to_string(), vec![])], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_v3_norm".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Struct("SuicVec3".to_string(), vec![])], + return_type: Type::Struct("SuicVec3".to_string(), vec![]), + }, + ); + + // Terrain functions + self.env.functions.insert( + "suic_simplex_noise_2d".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_fbm_noise".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float, Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_terrain_height".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_terrain_init".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "suic_terrain_draw".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Struct("SuicVec3".to_string(), vec![])], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "suic_terrain_cleanup".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Unit, + }, + ); + + // Lighting functions + self.env.functions.insert( + "suic_light_create".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float, Type::Float, Type::Float, Type::Float], + return_type: Type::Struct("SuicDirectionalLight".to_string(), vec![]), + }, + ); + self.env.functions.insert( + "suic_light_create_default".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Struct("SuicDirectionalLight".to_string(), vec![]), + }, + ); + self.env.functions.insert( + "suic_light_apply".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Struct("SuicColor".to_string(), vec![]), Type::Struct("SuicVec3".to_string(), vec![]), Type::Ptr(Box::new(Type::Struct("SuicDirectionalLight".to_string(), vec![])))], + return_type: Type::Struct("SuicColor".to_string(), vec![]), + }, + ); + self.env.functions.insert( + "suic_light_apply_shadows".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Struct("SuicColor".to_string(), vec![]), Type::Struct("SuicVec3".to_string(), vec![]), Type::Struct("SuicVec3".to_string(), vec![]), Type::Ptr(Box::new(Type::Struct("SuicDirectionalLight".to_string(), vec![])))], + return_type: Type::Struct("SuicColor".to_string(), vec![]), + }, + ); + self.env.functions.insert( + "suic_light_apply_shadows".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Struct("SuicColor".to_string(), vec![]), Type::Struct("SuicVec3".to_string(), vec![]), Type::Struct("SuicVec3".to_string(), vec![]), Type::Ptr(Box::new(Type::Struct("SuicDirectionalLight".to_string(), vec![])))], + return_type: Type::Struct("SuicColor".to_string(), vec![]), + }, + ); + + // Network functions + self.env.functions.insert( + "suic_net_init".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::String, Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_shutdown".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "suic_net_is_connected".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Bool, + }, + ); + self.env.functions.insert( + "suic_net_send_hello".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::String], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "suic_net_send_input".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int, Type::Float, Type::Float, Type::Float, Type::Float, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "suic_net_send_shoot".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "suic_net_poll".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Bool, + }, + ); + self.env.functions.insert( + "suic_net_get_my_id".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Int, + }, + ); + // Player accessor functions + self.env.functions.insert( + "suic_net_player_present".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Bool, + }, + ); + self.env.functions.insert( + "suic_net_player_alive".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Bool, + }, + ); + self.env.functions.insert( + "suic_net_player_hp".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_player_x".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_player_y".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_player_z".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_player_prev_x".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_player_prev_y".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_player_prev_z".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_player_yaw".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_player_pitch".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_player_weapon".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_player_pistol_mag".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_player_rifle_mag".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_player_pistol_ammo".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_player_rifle_ammo".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_player_medkits".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_player_reload_time".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_player_username".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::String, + }, + ); + // Item accessor functions + self.env.functions.insert( + "suic_net_item_present".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Bool, + }, + ); + self.env.functions.insert( + "suic_net_item_type".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_item_x".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_item_y".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_item_z".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int], + return_type: Type::Float, + }, + ); + // Room state functions + self.env.functions.insert( + "suic_net_room_state".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "suic_net_countdown".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Float, + }, + ); + self.env.functions.insert( + "suic_net_winner_name".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::String, + }, + ); + + // UI functions + self.env.functions.insert( + "suic_ui_username_prompt".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::String, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "suic_ui_draw_hud".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int, Type::Int, Type::Int, Type::Int, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "suic_ui_draw_crosshair".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int, Type::Float, Type::Bool], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "suic_ui_draw_room_state".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int, Type::Int, Type::Float, Type::String], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "suic_ui_draw_nameplate".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int, Type::Float, Type::Float, Type::Float, Type::String, Type::Bool, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float], + return_type: Type::Unit, + }, + ); + + // Additional Raylib functions + self.env.functions.insert( + "begin_mode3d".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "end_mode3d".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "draw_cube".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Int, Type::Int, Type::Int, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "draw_cube_wires".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Int, Type::Int, Type::Int, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "draw_sphere".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float, Type::Float, Type::Float, Type::Int, Type::Int, Type::Int, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "draw_capsule".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Float, Type::Int, Type::Int, Type::Int, Type::Int, Type::Int, Type::Int], + return_type: Type::Unit, + }, + ); + self.env.functions.insert( + "get_screen_width".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "get_screen_height".to_string(), + FunctionType { + type_params: vec![], + params: vec![], + return_type: Type::Int, + }, + ); + self.env.functions.insert( + "get_random_value".to_string(), + FunctionType { + type_params: vec![], + params: vec![Type::Int, Type::Int], + return_type: Type::Int, + }, + ); } fn collect_definitions(&mut self, node: &ASTNode) -> Result<(), TypeError> { @@ -1902,6 +2506,11 @@ impl TypeChecker { methods, }); } + ASTNodeKind::Const(c) => { + let ty = c.typ.as_ref().map(|t| self.type_annot_to_type(t)).unwrap_or(Type::Unknown); + let id = self.next_binding_id(); + self.env.add_var(id, c.name.clone(), ty, BindingKind::Default, node.span.clone()); + } _ => {} } Ok(()) @@ -1911,6 +2520,12 @@ impl TypeChecker { let ty = match &node.kind { ASTNodeKind::Const(c) => { let typed_value = self.typecheck_expr(&c.value)?; + + // Update the type in the environment + if let Some((id, _)) = self.env.get_var_by_name(&c.name) { + self.env.update_var_type(&id, typed_value.ty.clone()); + } + return Ok(TypedASTNode { kind: TypedASTNodeKind::Const(TypedConst { name: c.name.clone(), @@ -2569,51 +3184,17 @@ impl TypeChecker { ExprKind::Dot(obj, field) => { let typed_obj = self.typecheck_expr(obj)?; - let field_type = match &typed_obj.ty { - Type::Struct(name, _) => { - if let Some(type_info) = self.env.get_type(name) { - if let TypeInfoKind::Struct(fields) = &type_info.kind { - if let Some(field_ty) = fields - .iter() - .find(|(f, _)| f == field) - .map(|(_, ty)| self.type_annot_to_type(ty)) - { - field_ty - } else { - // Check for methods in impls - let mut method_type = None; - for impl_info in &self.env.impls { - if impl_info.target == *name { - if let Some(func_type) = impl_info.methods.get(field) { - method_type = Some(Type::Function( - func_type.params.clone(), - Box::new(func_type.return_type.clone()), - )); - break; - } - } - } - method_type.ok_or_else(|| TypeError { - kind: TypeErrorKind::UndefinedField( - field.clone(), - typed_obj.ty.clone(), - ), - span: expr.span.clone(), - })? - } - } else { - return Err(TypeError { - kind: TypeErrorKind::NotAStruct(typed_obj.ty.clone()), - span: obj.span.clone(), - }); - } - } else { + let (name, is_ptr) = match &typed_obj.ty { + Type::Struct(name, _) => (name.clone(), false), + Type::Ptr(inner) => match &**inner { + Type::Struct(name, _) => (name.clone(), true), + _ => { return Err(TypeError { - kind: TypeErrorKind::UndefinedType(name.clone()), + kind: TypeErrorKind::NotAStruct(typed_obj.ty.clone()), span: obj.span.clone(), }); } - } + }, ty => { return Err(TypeError { kind: TypeErrorKind::NotAStruct(ty.clone()), @@ -2622,8 +3203,66 @@ impl TypeChecker { } }; + let field_type = if let Some(type_info) = self.env.get_type(&name) { + if let TypeInfoKind::Struct(fields) = &type_info.kind { + if let Some(field_ty) = fields + .iter() + .find(|(f, _)| f == field) + .map(|(_, ty)| self.type_annot_to_type(ty)) + { + field_ty + } else { + // Check for methods in impls + let mut method_type = None; + for impl_info in &self.env.impls { + if impl_info.target == name { + if let Some(func_type) = impl_info.methods.get(field) { + method_type = Some(Type::Function( + func_type.params.clone(), + Box::new(func_type.return_type.clone()), + )); + break; + } + } + } + method_type.ok_or_else(|| TypeError { + kind: TypeErrorKind::UndefinedField( + field.clone(), + typed_obj.ty.clone(), + ), + span: expr.span.clone(), + })? + } + } else { + return Err(TypeError { + kind: TypeErrorKind::NotAStruct(typed_obj.ty.clone()), + span: obj.span.clone(), + }); + } + } else { + return Err(TypeError { + kind: TypeErrorKind::UndefinedType(name.clone()), + span: obj.span.clone(), + }); + }; + + let final_obj = if is_ptr { + let inner_ty = match &typed_obj.ty { + Type::Ptr(inner) => (**inner).clone(), + _ => unreachable!(), + }; + TypedExpr { + kind: TypedExprKind::UnOp(UnOp::Deref, Box::new(typed_obj.clone())), + span: typed_obj.span.clone(), + attributes: vec![], + ty: inner_ty, + } + } else { + typed_obj + }; + ( - TypedExprKind::Dot(Box::new(typed_obj), field.clone()), + TypedExprKind::Dot(Box::new(final_obj), field.clone()), field_type, ) } @@ -2817,31 +3456,40 @@ impl TypeChecker { } ExprKind::Assign(lhs, rhs) => { - // Check if lhs is a mutable variable - if let ExprKind::Variable(name) = &lhs.kind { - if let Some((_, var_info)) = self.env.get_var_by_name(name) { - if var_info.kind != BindingKind::Mutable { - return Err(TypeError { - kind: TypeErrorKind::MutableityError(format!( - "Cannot assign to immutable variable '{}'", - name - )), - span: lhs.span.clone(), - }); - } - } - } else { - return Err(TypeError { - kind: TypeErrorKind::MutableityError( - "Invalid left-hand side of assignment".to_string(), - ), - span: lhs.span.clone(), - }); - } - let typed_lhs = self.typecheck_expr(lhs)?; let typed_rhs = self.typecheck_expr(rhs)?; + // Check if lhs is a valid l-value and mutable if it's a variable + match &lhs.kind { + ExprKind::Variable(name) => { + if let Some((_, var_info)) = self.env.get_var_by_name(name) { + if var_info.kind != BindingKind::Mutable { + return Err(TypeError { + kind: TypeErrorKind::MutableityError(format!( + "Cannot assign to immutable variable '{}'", + name + )), + span: lhs.span.clone(), + }); + } + } + } + ExprKind::Dot(_, _) + | ExprKind::Index(_, _) + | ExprKind::UnOp(UnOp::Deref, _) => { + // Valid l-values. For now we don't check nested mutability + // (e.g. if the base struct is mutable), but pointers are always mutable. + } + _ => { + return Err(TypeError { + kind: TypeErrorKind::MutableityError( + "Invalid left-hand side of assignment".to_string(), + ), + span: lhs.span.clone(), + }); + } + } + if !self.types_compatible(&typed_rhs.ty, &typed_lhs.ty) { return Err(TypeError { kind: TypeErrorKind::TypeMismatch(typed_lhs.ty.clone(), typed_rhs.ty),