GAME READY

This commit is contained in:
Masashi 2025-12-20 17:11:21 +05:30
commit 5a60e1c62a
18 changed files with 2506 additions and 249 deletions

View file

@ -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"

222
compiler_out.txt Normal file

File diff suppressed because one or more lines are too long

BIN
game/client Executable file

Binary file not shown.

393
game/client.c Normal file
View file

@ -0,0 +1,393 @@
#include "libsuicmez/libsuicmez.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
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;
}

BIN
game/client.o Executable file

Binary file not shown.

View file

@ -1,22 +1,459 @@
# 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,
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
end

View file

@ -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();
}

View file

@ -1,5 +1,5 @@
#include "suic_app.h"
#include "runtime.h"
// #include "runtime.h" // Not available
#include <math.h>
const char* suic_item_name(uint8_t t) {

View file

@ -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");

457
game/junk Normal file
View file

@ -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

View file

@ -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); }

View file

@ -16,8 +16,12 @@
// ODE support
#include <ode/ode.h>
// 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;

View file

@ -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<CType, String> {
"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
}
}

View file

@ -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() {

View file

@ -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
)
}

View file

@ -123,6 +123,7 @@ fn main() {
if let Err(e) = run_file(&filename, args.debug) {
eprintln!("Error: {}", e);
std::process::exit(1);
}
} else {
eprintln!(

View file

@ -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(),

View file

@ -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),