Compare commits
4 commits
6f198b17a9
...
1006b231f0
| Author | SHA1 | Date | |
|---|---|---|---|
| 1006b231f0 | |||
| 3ee0c43cd1 | |||
| e9e7d938ef | |||
| 0d269b83e3 |
36 changed files with 1767 additions and 1162 deletions
28
game/build_client.sh
Executable file
28
game/build_client.sh
Executable file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Build the game client using the split modules
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# Get raylib compile and link flags
|
||||||
|
RAYLIB_CFLAGS=$(pkg-config --cflags raylib 2>/dev/null || echo "-I/usr/include")
|
||||||
|
RAYLIB_LIBS=$(pkg-config --libs raylib 2>/dev/null || echo "-lraylib -lm")
|
||||||
|
|
||||||
|
echo "Compiling game modules..."
|
||||||
|
gcc -c suic_math.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_math.o
|
||||||
|
gcc -c suic_terrain.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_terrain.o
|
||||||
|
gcc -c suic_lighting.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_lighting.o
|
||||||
|
gcc -c suic_net.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_net.o
|
||||||
|
gcc -c suic_ui.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_ui.o
|
||||||
|
gcc -c suic_game_client.c -I../libsuicmez $RAYLIB_CFLAGS -o suic_game_client.o
|
||||||
|
|
||||||
|
echo "Compiling main entry point..."
|
||||||
|
gcc -c main.c -I../libsuicmez $RAYLIB_CFLAGS -o main.o
|
||||||
|
|
||||||
|
echo "Linking..."
|
||||||
|
gcc main.o suic_math.o suic_terrain.o suic_lighting.o suic_net.o suic_ui.o suic_game_client.o \
|
||||||
|
$RAYLIB_LIBS -lm -o game_client
|
||||||
|
|
||||||
|
echo "Successfully built: game_client"
|
||||||
|
echo "Run with: ./game_client"
|
||||||
BIN
game/client
BIN
game/client
Binary file not shown.
1194
game/client.c
1194
game/client.c
File diff suppressed because it is too large
Load diff
BIN
game/client.o
BIN
game/client.o
Binary file not shown.
|
|
@ -0,0 +1,22 @@
|
||||||
|
fn main() -> int do
|
||||||
|
init_window(1280, 720, "Voxel Shooter Client")
|
||||||
|
defer close_window()
|
||||||
|
set_target_fps(120)
|
||||||
|
disable_cursor()
|
||||||
|
|
||||||
|
# Main loop
|
||||||
|
while window_should_close() == false do
|
||||||
|
let dt = get_frame_time()
|
||||||
|
|
||||||
|
# Rendering
|
||||||
|
begin_drawing()
|
||||||
|
clear_background(135, 206, 235, 255)
|
||||||
|
|
||||||
|
# Draw FPS
|
||||||
|
draw_fps(10, 10)
|
||||||
|
|
||||||
|
end_drawing()
|
||||||
|
end
|
||||||
|
|
||||||
|
0
|
||||||
|
end
|
||||||
BIN
game/game_client
Executable file
BIN
game/game_client
Executable file
Binary file not shown.
41
game/main.c
Normal file
41
game/main.c
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* Game Client Main Entry Point
|
||||||
|
* Uses the split modules: suic_game_client, suic_terrain, suic_lighting, suic_net, suic_ui
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "suic_game_client.h"
|
||||||
|
#include "suic_ui.h"
|
||||||
|
#include "raylib.h"
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
const int screenW = 1280, screenH = 720;
|
||||||
|
InitWindow(screenW, screenH, "Voxel Shooter - Client");
|
||||||
|
SetTargetFPS(120);
|
||||||
|
|
||||||
|
/* Get username */
|
||||||
|
char username[SUIC_NET_USERNAME_MAX] = {0};
|
||||||
|
suic_ui_username_prompt(username, SUIC_NET_USERNAME_MAX);
|
||||||
|
|
||||||
|
/* Initialize game subsystems */
|
||||||
|
suic_game_init(username, "127.0.0.1", 27015);
|
||||||
|
|
||||||
|
/* Main loop */
|
||||||
|
while (!WindowShouldClose()) {
|
||||||
|
float dt = GetFrameTime();
|
||||||
|
|
||||||
|
suic_game_handle_input(dt);
|
||||||
|
suic_game_update(dt);
|
||||||
|
|
||||||
|
BeginDrawing();
|
||||||
|
ClearBackground((Color){135, 206, 235, 255});
|
||||||
|
|
||||||
|
suic_game_render();
|
||||||
|
|
||||||
|
EndDrawing();
|
||||||
|
}
|
||||||
|
|
||||||
|
suic_game_shutdown();
|
||||||
|
CloseWindow();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
BIN
game/main.o
Normal file
BIN
game/main.o
Normal file
Binary file not shown.
21
game/runtime.h
Normal file
21
game/runtime.h
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../libsuicmez/suicmez_gc.h"
|
||||||
|
|
||||||
|
void* gc_alloc(const TypeInfo* type, size_t size);
|
||||||
|
void gc_init(void);
|
||||||
|
void gc_shutdown(void);
|
||||||
|
|
||||||
|
// Helper for allocating arrays
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
}
|
||||||
BIN
game/runtime.h.gch
Normal file
BIN
game/runtime.h.gch
Normal file
Binary file not shown.
28
game/suic_app.c
Normal file
28
game/suic_app.c
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#include "suic_app.h"
|
||||||
|
#include "../libsuicmez/libsuicmez.h"
|
||||||
|
#include "runtime.h"
|
||||||
|
|
||||||
|
void suic_app_init(int width, int height, const char* title) {
|
||||||
|
suic_init_window(width, height, (char*)title);
|
||||||
|
suic_set_target_fps(120);
|
||||||
|
suic_disable_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool suic_app_should_close(void) {
|
||||||
|
return suic_window_should_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_app_frame_begin(void) {
|
||||||
|
suic_begin_drawing();
|
||||||
|
suic_clear_background(135, 206, 235, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_app_frame_end(void) {
|
||||||
|
draw_fps(10, 40);
|
||||||
|
suic_end_drawing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_app_shutdown(void) {
|
||||||
|
suic_close_window();
|
||||||
|
}
|
||||||
|
|
||||||
25
game/suic_app.h
Normal file
25
game/suic_app.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/* ---- Lifecycle ---- */
|
||||||
|
void suic_runtime_init(void);
|
||||||
|
void suic_runtime_shutdown(void);
|
||||||
|
|
||||||
|
/* ---- Window / Loop ---- */
|
||||||
|
void suic_app_init(int width, int height, const char* title);
|
||||||
|
bool suic_app_should_close(void);
|
||||||
|
void suic_app_frame_begin(void);
|
||||||
|
void suic_app_frame_end(void);
|
||||||
|
void suic_app_shutdown(void);
|
||||||
|
|
||||||
|
/* ---- Drawing ---- */
|
||||||
|
void suic_draw_hello(void);
|
||||||
|
|
||||||
|
/* ---- Gameplay helpers ---- */
|
||||||
|
const char* suic_item_name(uint8_t t);
|
||||||
|
struct Color* suic_apply_directional_lighting(
|
||||||
|
struct Color* baseColor,
|
||||||
|
struct Vector3* normal,
|
||||||
|
struct DirectionalLight* light
|
||||||
|
);
|
||||||
19
game/suic_game.sui
Normal file
19
game/suic_game.sui
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
fn main() -> int do
|
||||||
|
suic_runtime_init()
|
||||||
|
defer suic_runtime_shutdown()
|
||||||
|
|
||||||
|
# Initialize application / window
|
||||||
|
suic_app_init(1280, 720, "Voxel Shooter - Client")
|
||||||
|
defer suic_app_shutdown()
|
||||||
|
|
||||||
|
# Main loop
|
||||||
|
while not suic_app_should_close() do
|
||||||
|
suic_app_frame_begin()
|
||||||
|
|
||||||
|
suic_draw_hello()
|
||||||
|
|
||||||
|
suic_app_frame_end()
|
||||||
|
end
|
||||||
|
|
||||||
|
0
|
||||||
|
end
|
||||||
280
game/suic_game_client.c
Normal file
280
game/suic_game_client.c
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
#include "suic_game_client.h"
|
||||||
|
#include "raylib.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* ===== GAME STATE ===== */
|
||||||
|
|
||||||
|
static SuicVec3 g_camPos = {0, 5.0f, 6};
|
||||||
|
static float g_yaw = 0.0f, g_pitch = 0.0f;
|
||||||
|
static SuicVec3 g_prevBodyPos = {0, 5.0f, 6};
|
||||||
|
static SuicVec3 g_currentTargetBody = {0, 5.0f, 6};
|
||||||
|
static float g_interpTimer = 0.0f;
|
||||||
|
|
||||||
|
static float g_recoilYaw = 0.0f, g_recoilPitch = 0.0f, g_crossSpread = 0.0f;
|
||||||
|
static bool g_scoped = false;
|
||||||
|
static float g_fireCooldown = 0.0f;
|
||||||
|
static int g_shotsInBurst = 0;
|
||||||
|
static float g_burstResetTimer = 0.0f;
|
||||||
|
static uint32_t g_clientTick = 0;
|
||||||
|
|
||||||
|
static SuicDirectionalLight g_light;
|
||||||
|
|
||||||
|
/* Weapon constants */
|
||||||
|
static const float PISTOL_FIRE_RATE = 4.0f, RIFLE_FIRE_RATE = 12.0f;
|
||||||
|
static const float RECOIL_RETURN = 18.0f, CROSS_RETURN = 14.0f;
|
||||||
|
static const float PISTOL_KICK_PITCH = 0.010f, PISTOL_KICK_YAW = 0.004f;
|
||||||
|
static const float RIFLE_KICK_PITCH = 0.018f, RIFLE_KICK_YAW = 0.010f;
|
||||||
|
static const float PISTOL_CROSS_KICK = 2.0f, RIFLE_CROSS_KICK = 4.0f;
|
||||||
|
static const float PISTOL_SPRAY_GROW = 0.4f, RIFLE_SPRAY_GROW = 1.2f;
|
||||||
|
static const float BURST_RESET_TIME = 0.18f;
|
||||||
|
|
||||||
|
/* ===== LIFECYCLE ===== */
|
||||||
|
|
||||||
|
void suic_game_init(const char* username, const char* serverIp, int port) {
|
||||||
|
suic_terrain_init();
|
||||||
|
suic_net_init(serverIp, port);
|
||||||
|
suic_net_send_hello(username);
|
||||||
|
g_light = suic_light_create_default();
|
||||||
|
g_camPos = suic_v3(0, 5.0f, 6);
|
||||||
|
g_yaw = 0.0f;
|
||||||
|
g_pitch = 0.0f;
|
||||||
|
DisableCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_game_shutdown(void) {
|
||||||
|
suic_terrain_cleanup();
|
||||||
|
suic_net_shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== INPUT HANDLING ===== */
|
||||||
|
|
||||||
|
void suic_game_handle_input(float dt) {
|
||||||
|
g_clientTick++;
|
||||||
|
|
||||||
|
/* Cooldowns */
|
||||||
|
g_fireCooldown -= dt;
|
||||||
|
if (g_fireCooldown < 0.0f) g_fireCooldown = 0.0f;
|
||||||
|
g_burstResetTimer -= dt;
|
||||||
|
if (g_burstResetTimer <= 0.0f) g_shotsInBurst = 0;
|
||||||
|
|
||||||
|
/* Recoil recovery */
|
||||||
|
float k = 1.0f - expf(-RECOIL_RETURN * dt);
|
||||||
|
g_recoilYaw += (0.0f - g_recoilYaw) * k;
|
||||||
|
g_recoilPitch += (0.0f - g_recoilPitch) * k;
|
||||||
|
|
||||||
|
float k2 = 1.0f - expf(-CROSS_RETURN * dt);
|
||||||
|
g_crossSpread += (0.0f - g_crossSpread) * k2;
|
||||||
|
if (g_crossSpread < 0.01f) g_crossSpread = 0.0f;
|
||||||
|
|
||||||
|
/* Mouse look */
|
||||||
|
Vector2 md = GetMouseDelta();
|
||||||
|
const float sens = 0.0025f;
|
||||||
|
g_yaw -= md.x * sens;
|
||||||
|
g_pitch -= md.y * sens;
|
||||||
|
if (g_pitch < -1.5f) g_pitch = -1.5f;
|
||||||
|
if (g_pitch > 1.5f) g_pitch = 1.5f;
|
||||||
|
|
||||||
|
/* Movement input */
|
||||||
|
float moveX = 0.0f, moveZ = 0.0f;
|
||||||
|
if (IsKeyDown(KEY_A)) moveX += 1.0f;
|
||||||
|
if (IsKeyDown(KEY_D)) moveX -= 1.0f;
|
||||||
|
if (IsKeyDown(KEY_W)) moveZ += 1.0f;
|
||||||
|
if (IsKeyDown(KEY_S)) moveZ -= 1.0f;
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
uint8_t buttons = 0;
|
||||||
|
if (IsKeyPressed(KEY_ONE)) buttons |= SUIC_BTN_SWITCH_PISTOL;
|
||||||
|
if (IsKeyPressed(KEY_TWO)) buttons |= SUIC_BTN_SWITCH_RIFLE;
|
||||||
|
if (IsKeyPressed(KEY_R)) buttons |= SUIC_BTN_RELOAD;
|
||||||
|
if (IsKeyPressed(KEY_F)) buttons |= SUIC_BTN_PICK;
|
||||||
|
if (IsKeyPressed(KEY_H)) buttons |= SUIC_BTN_USE_MEDKIT;
|
||||||
|
if (IsKeyPressed(KEY_SPACE)) buttons |= SUIC_BTN_JUMP;
|
||||||
|
if (IsKeyPressed(KEY_Z)) g_scoped = !g_scoped;
|
||||||
|
|
||||||
|
uint8_t myId = suic_net_get_my_id();
|
||||||
|
float viewYaw = g_yaw + g_recoilYaw;
|
||||||
|
float viewPitch = g_pitch + g_recoilPitch;
|
||||||
|
if (viewPitch < -1.5f) viewPitch = -1.5f;
|
||||||
|
if (viewPitch > 1.5f) viewPitch = 1.5f;
|
||||||
|
|
||||||
|
if (myId != 255) {
|
||||||
|
suic_net_send_input(myId, g_clientTick, moveX, moveZ, viewYaw, viewPitch, buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shooting */
|
||||||
|
if (myId != 255 && IsMouseButtonDown(MOUSE_BUTTON_LEFT) && g_fireCooldown <= 0.0f &&
|
||||||
|
suic_net_player_present(myId)) {
|
||||||
|
int wpn = suic_net_player_weapon(myId);
|
||||||
|
float rate = (wpn == SUIC_WEAPON_PISTOL) ? PISTOL_FIRE_RATE : RIFLE_FIRE_RATE;
|
||||||
|
g_fireCooldown = 1.0f / rate;
|
||||||
|
|
||||||
|
g_shotsInBurst++;
|
||||||
|
g_burstResetTimer = BURST_RESET_TIME;
|
||||||
|
|
||||||
|
if (wpn == SUIC_WEAPON_PISTOL) {
|
||||||
|
g_recoilPitch += PISTOL_KICK_PITCH;
|
||||||
|
g_recoilYaw += (((float)GetRandomValue(-1000, 1000)) / 1000.0f) * PISTOL_KICK_YAW;
|
||||||
|
g_crossSpread += PISTOL_CROSS_KICK + g_shotsInBurst * PISTOL_SPRAY_GROW;
|
||||||
|
} else {
|
||||||
|
g_recoilPitch += RIFLE_KICK_PITCH;
|
||||||
|
g_recoilYaw += (((float)GetRandomValue(-1000, 1000)) / 1000.0f) * RIFLE_KICK_YAW;
|
||||||
|
g_crossSpread += RIFLE_CROSS_KICK + g_shotsInBurst * RIFLE_SPRAY_GROW;
|
||||||
|
}
|
||||||
|
|
||||||
|
suic_net_send_shoot(myId, g_clientTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== UPDATE ===== */
|
||||||
|
|
||||||
|
void suic_game_update(float dt) {
|
||||||
|
suic_net_poll();
|
||||||
|
|
||||||
|
uint8_t myId = suic_net_get_my_id();
|
||||||
|
if (myId != 255 && suic_net_player_present(myId)) {
|
||||||
|
g_prevBodyPos = g_currentTargetBody;
|
||||||
|
g_currentTargetBody = suic_v3(suic_net_player_x(myId),
|
||||||
|
suic_net_player_y(myId),
|
||||||
|
suic_net_player_z(myId));
|
||||||
|
g_interpTimer = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interpolate camera */
|
||||||
|
if (myId != 255 && suic_net_player_present(myId)) {
|
||||||
|
float interpFactor = g_interpTimer / (1.0f / 20.0f);
|
||||||
|
if (interpFactor > 1.0f) interpFactor = 1.0f;
|
||||||
|
SuicVec3 interpBody = suic_v3_add(suic_v3_mul(g_prevBodyPos, 1.0f - interpFactor),
|
||||||
|
suic_v3_mul(g_currentTargetBody, interpFactor));
|
||||||
|
g_camPos.x = interpBody.x;
|
||||||
|
g_camPos.y = interpBody.y + 1.0f;
|
||||||
|
g_camPos.z = interpBody.z + 0.0001f;
|
||||||
|
|
||||||
|
float terrain_h = suic_terrain_height(g_camPos.x, g_camPos.z);
|
||||||
|
g_camPos.y = fmaxf(g_camPos.y, terrain_h + 1.5f);
|
||||||
|
}
|
||||||
|
g_interpTimer += dt;
|
||||||
|
|
||||||
|
/* Forward offset */
|
||||||
|
float viewYaw = g_yaw + g_recoilYaw;
|
||||||
|
float viewPitch = g_pitch + g_recoilPitch;
|
||||||
|
SuicVec3 forward = suic_v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch),
|
||||||
|
cosf(viewYaw) * cosf(viewPitch));
|
||||||
|
g_camPos = suic_v3_add(g_camPos, suic_v3_mul(forward, 0.3f));
|
||||||
|
g_camPos.y -= 0.2f;
|
||||||
|
|
||||||
|
float terrain_h = suic_terrain_height(g_camPos.x, g_camPos.z);
|
||||||
|
g_camPos.y = fmaxf(g_camPos.y, terrain_h + 1.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== RENDERING ===== */
|
||||||
|
|
||||||
|
void suic_game_render(void) {
|
||||||
|
int sw = GetScreenWidth();
|
||||||
|
int sh = GetScreenHeight();
|
||||||
|
uint8_t myId = suic_net_get_my_id();
|
||||||
|
|
||||||
|
float viewYaw = g_yaw + g_recoilYaw;
|
||||||
|
float viewPitch = g_pitch + g_recoilPitch;
|
||||||
|
if (viewPitch < -1.5f) viewPitch = -1.5f;
|
||||||
|
if (viewPitch > 1.5f) viewPitch = 1.5f;
|
||||||
|
|
||||||
|
SuicVec3 forward = suic_v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch),
|
||||||
|
cosf(viewYaw) * cosf(viewPitch));
|
||||||
|
|
||||||
|
Camera3D cam = {0};
|
||||||
|
cam.position = (Vector3){g_camPos.x, g_camPos.y, g_camPos.z};
|
||||||
|
cam.target = (Vector3){g_camPos.x + forward.x, g_camPos.y + forward.y, g_camPos.z + forward.z};
|
||||||
|
cam.up = (Vector3){0, 1, 0};
|
||||||
|
cam.fovy = g_scoped ? 30.0f : 75.0f;
|
||||||
|
cam.projection = CAMERA_PERSPECTIVE;
|
||||||
|
|
||||||
|
BeginMode3D(cam);
|
||||||
|
|
||||||
|
/* Terrain */
|
||||||
|
suic_terrain_draw(g_camPos);
|
||||||
|
|
||||||
|
/* Border */
|
||||||
|
float borderSize = SUIC_TERRAIN_MAX - SUIC_TERRAIN_MIN;
|
||||||
|
DrawCube((Vector3){0, 0, 0}, borderSize, 1000, borderSize, (Color){255, 0, 0, 100});
|
||||||
|
|
||||||
|
/* Items */
|
||||||
|
for (int i = 0; i < SUIC_NET_MAX_ITEMS; i++) {
|
||||||
|
if (!suic_net_item_present(i)) continue;
|
||||||
|
Color ic = (Color){220, 220, 220, 255};
|
||||||
|
uint8_t itemType = suic_net_item_type(i);
|
||||||
|
if (itemType == SUIC_ITEM_MEDKIT) ic = (Color){120, 255, 120, 255};
|
||||||
|
if (itemType == SUIC_ITEM_AMMO_PISTOL) ic = (Color){255, 220, 120, 255};
|
||||||
|
if (itemType == SUIC_ITEM_AMMO_RIFLE) ic = (Color){255, 180, 120, 255};
|
||||||
|
|
||||||
|
SuicVec3 itemPos = suic_v3(suic_net_item_x(i), suic_net_item_y(i), suic_net_item_z(i));
|
||||||
|
SuicVec3 itemNormal = suic_v3(0, 1, 0);
|
||||||
|
SuicColor litColor = suic_light_apply_shadows((SuicColor){ic.r, ic.g, ic.b, ic.a},
|
||||||
|
itemNormal, itemPos, &g_light);
|
||||||
|
DrawSphere((Vector3){itemPos.x, itemPos.y, itemPos.z}, 0.3f,
|
||||||
|
(Color){litColor.r, litColor.g, litColor.b, litColor.a});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Players */
|
||||||
|
for (int i = 0; i < SUIC_NET_MAX_PLAYERS; i++) {
|
||||||
|
if (!suic_net_player_present(i)) continue;
|
||||||
|
float interpFactor = g_interpTimer / (1.0f / 20.0f);
|
||||||
|
if (interpFactor > 1.0f) interpFactor = 1.0f;
|
||||||
|
SuicVec3 prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i));
|
||||||
|
SuicVec3 curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i));
|
||||||
|
SuicVec3 p = suic_v3_add(suic_v3_mul(prev, 1.0f - interpFactor), suic_v3_mul(curr, interpFactor));
|
||||||
|
|
||||||
|
Color c = (i == myId) ? (Color){80, 180, 255, 255} : (Color){255, 80, 80, 255};
|
||||||
|
if (!suic_net_player_alive(i)) c = (Color){120, 120, 120, 255};
|
||||||
|
|
||||||
|
SuicVec3 playerNormal = suic_v3(0, 1, 0);
|
||||||
|
SuicColor litColor = suic_light_apply_shadows((SuicColor){c.r, c.g, c.b, c.a},
|
||||||
|
playerNormal, p, &g_light);
|
||||||
|
DrawCapsule((Vector3){p.x, p.y - 0.5f, p.z}, (Vector3){p.x, p.y + 0.5f, p.z}, 0.35f, 8, 8,
|
||||||
|
(Color){litColor.r, litColor.g, litColor.b, litColor.a});
|
||||||
|
}
|
||||||
|
|
||||||
|
EndMode3D();
|
||||||
|
|
||||||
|
/* Nameplates */
|
||||||
|
for (int i = 0; i < SUIC_NET_MAX_PLAYERS; i++) {
|
||||||
|
if (!suic_net_player_present(i)) continue;
|
||||||
|
float interpFactor = g_interpTimer / (1.0f / 20.0f);
|
||||||
|
if (interpFactor > 1.0f) interpFactor = 1.0f;
|
||||||
|
SuicVec3 prev = suic_v3(suic_net_player_prev_x(i), suic_net_player_prev_y(i), suic_net_player_prev_z(i));
|
||||||
|
SuicVec3 curr = suic_v3(suic_net_player_x(i), suic_net_player_y(i), suic_net_player_z(i));
|
||||||
|
SuicVec3 p = suic_v3_add(suic_v3_mul(prev, 1.0f - interpFactor), suic_v3_mul(curr, interpFactor));
|
||||||
|
|
||||||
|
suic_ui_draw_nameplate(sw, sh, p.x, p.y, p.z, suic_net_player_username(i), i == myId,
|
||||||
|
g_camPos.x, g_camPos.y, g_camPos.z,
|
||||||
|
g_camPos.x + forward.x, g_camPos.y + forward.y, g_camPos.z + forward.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HUD */
|
||||||
|
if (myId == 255 || !suic_net_player_present(myId)) {
|
||||||
|
DrawText("Connecting...", 10, 10, 20, RAYWHITE);
|
||||||
|
} else {
|
||||||
|
int weapon = suic_net_player_weapon(myId);
|
||||||
|
int mag = (weapon == SUIC_WEAPON_PISTOL) ? suic_net_player_pistol_mag(myId)
|
||||||
|
: suic_net_player_rifle_mag(myId);
|
||||||
|
int reserve = (weapon == SUIC_WEAPON_PISTOL) ? suic_net_player_pistol_ammo(myId)
|
||||||
|
: suic_net_player_rifle_ammo(myId);
|
||||||
|
suic_ui_draw_hud(suic_net_player_hp(myId), weapon, mag, reserve,
|
||||||
|
suic_net_player_medkits(myId), suic_net_player_reload_time(myId));
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawFPS(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, g_crossSpread, g_scoped);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== ACCESSORS ===== */
|
||||||
|
|
||||||
|
SuicVec3 suic_game_get_camera_pos(void) { return g_camPos; }
|
||||||
|
float suic_game_get_yaw(void) { return g_yaw; }
|
||||||
|
float suic_game_get_pitch(void) { return g_pitch; }
|
||||||
|
bool suic_game_is_scoped(void) { return g_scoped; }
|
||||||
27
game/suic_game_client.h
Normal file
27
game/suic_game_client.h
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
#include "suic_math.h"
|
||||||
|
#include "suic_terrain.h"
|
||||||
|
#include "suic_lighting.h"
|
||||||
|
#include "suic_net.h"
|
||||||
|
#include "suic_ui.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* High-level game client API.
|
||||||
|
* Ties together terrain, networking, lighting, and rendering.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Game lifecycle */
|
||||||
|
void suic_game_init(const char* username, const char* serverIp, int port);
|
||||||
|
void suic_game_update(float dt);
|
||||||
|
void suic_game_render(void);
|
||||||
|
void suic_game_shutdown(void);
|
||||||
|
|
||||||
|
/* Camera state */
|
||||||
|
SuicVec3 suic_game_get_camera_pos(void);
|
||||||
|
float suic_game_get_yaw(void);
|
||||||
|
float suic_game_get_pitch(void);
|
||||||
|
bool suic_game_is_scoped(void);
|
||||||
|
|
||||||
|
/* Input state */
|
||||||
|
void suic_game_handle_input(float dt);
|
||||||
BIN
game/suic_game_client.o
Normal file
BIN
game/suic_game_client.o
Normal file
Binary file not shown.
39
game/suic_gameplay.c
Normal file
39
game/suic_gameplay.c
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#include "suic_app.h"
|
||||||
|
#include "runtime.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
const char* suic_item_name(uint8_t t) {
|
||||||
|
if (t == 1) return "Medkit";
|
||||||
|
if (t == 2) return "Pistol ammo";
|
||||||
|
if (t == 3) return "Rifle ammo";
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Color* suic_apply_directional_lighting(
|
||||||
|
struct Color* baseColor,
|
||||||
|
struct Vector3* normal,
|
||||||
|
struct DirectionalLight* light
|
||||||
|
) {
|
||||||
|
struct Vector3* lightDir = light->direction;
|
||||||
|
|
||||||
|
float diff = fmaxf(
|
||||||
|
0.0f,
|
||||||
|
-(lightDir->x * normal->x +
|
||||||
|
lightDir->y * normal->y +
|
||||||
|
lightDir->z * normal->z)
|
||||||
|
);
|
||||||
|
|
||||||
|
float brightness =
|
||||||
|
light->ambientIntensity +
|
||||||
|
diff * light->intensity * (1.0f - light->ambientIntensity);
|
||||||
|
|
||||||
|
uint8_t r = (uint8_t)(baseColor->r * brightness);
|
||||||
|
uint8_t g = (uint8_t)(baseColor->g * brightness);
|
||||||
|
uint8_t b = (uint8_t)(baseColor->b * brightness);
|
||||||
|
|
||||||
|
return suic_alloc_struct(
|
||||||
|
&sui_typeinfo_Color,
|
||||||
|
sizeof(struct Color),
|
||||||
|
&(struct Color){ r, g, b, baseColor->a }
|
||||||
|
);
|
||||||
|
}
|
||||||
150
game/suic_lighting.c
Normal file
150
game/suic_lighting.c
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
#include "suic_lighting.h"
|
||||||
|
#include "suic_terrain.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
/* ===== LIGHT CREATION ===== */
|
||||||
|
|
||||||
|
SuicDirectionalLight suic_light_create(float dir_x, float dir_y, float dir_z,
|
||||||
|
float intensity, float ambient) {
|
||||||
|
SuicDirectionalLight light = {0};
|
||||||
|
light.direction = suic_v3_norm(suic_v3(dir_x, dir_y, dir_z));
|
||||||
|
light.color = suic_v3(1.0f, 1.0f, 1.0f);
|
||||||
|
light.intensity = intensity;
|
||||||
|
light.ambientIntensity = ambient;
|
||||||
|
light.shadowBias = 0.005f;
|
||||||
|
light.shadowIntensity = 0.4f;
|
||||||
|
return light;
|
||||||
|
}
|
||||||
|
|
||||||
|
SuicDirectionalLight suic_light_create_default(void) {
|
||||||
|
return suic_light_create(-0.8f, -1.0f, -0.6f, 1.2f, 0.3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== BASIC LIGHTING ===== */
|
||||||
|
|
||||||
|
SuicColor suic_light_apply(SuicColor base, SuicVec3 normal, SuicDirectionalLight* light) {
|
||||||
|
SuicVec3 lightDir = light->direction;
|
||||||
|
|
||||||
|
/* Diffuse: Lambertian */
|
||||||
|
float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y +
|
||||||
|
lightDir.z * normal.z));
|
||||||
|
|
||||||
|
float brightness = light->ambientIntensity +
|
||||||
|
(diff * light->intensity * (1.0f - light->ambientIntensity));
|
||||||
|
|
||||||
|
int r = (int)(base.r * brightness);
|
||||||
|
int g = (int)(base.g * brightness);
|
||||||
|
int b = (int)(base.b * brightness);
|
||||||
|
|
||||||
|
if (r > 255) r = 255;
|
||||||
|
if (g > 255) g = 255;
|
||||||
|
if (b > 255) b = 255;
|
||||||
|
|
||||||
|
return (SuicColor){r, g, b, base.a};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== SHADOW CALCULATIONS ===== */
|
||||||
|
|
||||||
|
float suic_shadow_factor(SuicVec3 worldPos, SuicVec3 lightDir, float maxDist) {
|
||||||
|
float distFromLight = fmaxf(0.0f, worldPos.y - 2.0f);
|
||||||
|
float shadowIntensity = fmaxf(0.0f, 1.0f - (distFromLight / maxDist));
|
||||||
|
return 1.0f - (shadowIntensity * 0.15f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float suic_shadow_temporal(SuicVec3 worldPos, float timePhase) {
|
||||||
|
float noiseVal = sinf(worldPos.x * 0.5f + timePhase) *
|
||||||
|
cosf(worldPos.z * 0.5f + timePhase);
|
||||||
|
return 1.0f + (noiseVal * 0.02f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== LIGHTING WITH SHADOWS ===== */
|
||||||
|
|
||||||
|
SuicColor suic_light_apply_shadows(SuicColor base, SuicVec3 normal, SuicVec3 worldPos,
|
||||||
|
SuicDirectionalLight* light) {
|
||||||
|
float shadowFactor = suic_shadow_factor(worldPos, light->direction, 10.0f);
|
||||||
|
float adjustedIntensity = light->intensity * shadowFactor;
|
||||||
|
|
||||||
|
SuicVec3 lightDir = light->direction;
|
||||||
|
float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y +
|
||||||
|
lightDir.z * normal.z));
|
||||||
|
|
||||||
|
float brightness = light->ambientIntensity +
|
||||||
|
(diff * adjustedIntensity * (1.0f - light->ambientIntensity));
|
||||||
|
|
||||||
|
int r = (int)(base.r * brightness);
|
||||||
|
int g = (int)(base.g * brightness);
|
||||||
|
int b = (int)(base.b * brightness);
|
||||||
|
|
||||||
|
if (r > 255) r = 255;
|
||||||
|
if (g > 255) g = 255;
|
||||||
|
if (b > 255) b = 255;
|
||||||
|
|
||||||
|
return (SuicColor){r, g, b, base.a};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== AMBIENT OCCLUSION ===== */
|
||||||
|
|
||||||
|
float suic_ao_calculate(SuicVec3 worldPos, SuicVec3 normal) {
|
||||||
|
float sampleRadius = 3.0f;
|
||||||
|
float aoAccum = 0.0f;
|
||||||
|
int numSamples = 8;
|
||||||
|
float centerHeight = worldPos.y;
|
||||||
|
float pi = 3.14159265f;
|
||||||
|
|
||||||
|
for (int i = 0; i < numSamples; i++) {
|
||||||
|
float angle = (2.0f * pi * (float)i) / (float)numSamples;
|
||||||
|
float sx = worldPos.x + cosf(angle) * sampleRadius;
|
||||||
|
float sz = worldPos.z + sinf(angle) * sampleRadius;
|
||||||
|
float sh = suic_terrain_height(sx, sz);
|
||||||
|
|
||||||
|
float heightDiff = sh - centerHeight;
|
||||||
|
if (heightDiff > 0.1f) {
|
||||||
|
float aoAmount = fminf(1.0f, heightDiff / 2.0f);
|
||||||
|
aoAccum += aoAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float aoFactor = 1.0f - (aoAccum / (float)numSamples) * 0.6f;
|
||||||
|
return fmaxf(0.2f, aoFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== PHONG LIGHTING ===== */
|
||||||
|
|
||||||
|
SuicColor suic_light_apply_phong(SuicColor base, SuicVec3 normal, SuicVec3 worldPos,
|
||||||
|
SuicVec3 camPos, SuicDirectionalLight* light) {
|
||||||
|
SuicVec3 lightDir = suic_v3_norm(light->direction);
|
||||||
|
SuicVec3 normal_n = suic_v3_norm(normal);
|
||||||
|
SuicVec3 viewDir = suic_v3_norm(suic_v3_sub(camPos, worldPos));
|
||||||
|
|
||||||
|
/* Diffuse */
|
||||||
|
float diffuse = fmaxf(0.0f, -suic_v3_dot(lightDir, normal_n));
|
||||||
|
|
||||||
|
/* Specular: Blinn-Phong */
|
||||||
|
SuicVec3 negLight = suic_v3(-lightDir.x, -lightDir.y, -lightDir.z);
|
||||||
|
SuicVec3 halfVec = suic_v3_norm(suic_v3_add(suic_v3_norm(negLight), viewDir));
|
||||||
|
float specular = powf(fmaxf(0.0f, suic_v3_dot(halfVec, normal_n)), 32.0f) * 0.5f;
|
||||||
|
|
||||||
|
/* Ambient occlusion */
|
||||||
|
float ao = suic_ao_calculate(worldPos, normal_n);
|
||||||
|
|
||||||
|
/* Shadow */
|
||||||
|
float shadowFactor = suic_shadow_factor(worldPos, light->direction, 10.0f);
|
||||||
|
|
||||||
|
/* Combine */
|
||||||
|
float brightness = light->ambientIntensity * ao;
|
||||||
|
brightness += diffuse * light->intensity * shadowFactor *
|
||||||
|
(1.0f - light->ambientIntensity) * ao;
|
||||||
|
brightness += specular * light->intensity * shadowFactor * 0.6f;
|
||||||
|
|
||||||
|
brightness = fminf(1.0f, brightness);
|
||||||
|
|
||||||
|
int r = (int)(base.r * brightness);
|
||||||
|
int g = (int)(base.g * brightness);
|
||||||
|
int b = (int)(base.b * brightness);
|
||||||
|
|
||||||
|
if (r > 255) r = 255;
|
||||||
|
if (g > 255) g = 255;
|
||||||
|
if (b > 255) b = 255;
|
||||||
|
|
||||||
|
return (SuicColor){r, g, b, base.a};
|
||||||
|
}
|
||||||
44
game/suic_lighting.h
Normal file
44
game/suic_lighting.h
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#pragma once
|
||||||
|
#include "suic_math.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lighting system API.
|
||||||
|
* Supports directional lighting, shadows, ambient occlusion, and Phong shading.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct SuicColor {
|
||||||
|
uint8_t r, g, b, a;
|
||||||
|
} SuicColor;
|
||||||
|
|
||||||
|
typedef struct SuicDirectionalLight {
|
||||||
|
SuicVec3 direction;
|
||||||
|
SuicVec3 color;
|
||||||
|
float intensity;
|
||||||
|
float ambientIntensity;
|
||||||
|
float shadowBias;
|
||||||
|
float shadowIntensity;
|
||||||
|
} SuicDirectionalLight;
|
||||||
|
|
||||||
|
/* Light creation */
|
||||||
|
SuicDirectionalLight suic_light_create(float dir_x, float dir_y, float dir_z,
|
||||||
|
float intensity, float ambient);
|
||||||
|
SuicDirectionalLight suic_light_create_default(void);
|
||||||
|
|
||||||
|
/* Basic lighting */
|
||||||
|
SuicColor suic_light_apply(SuicColor base, SuicVec3 normal, SuicDirectionalLight* light);
|
||||||
|
|
||||||
|
/* Lighting with shadows */
|
||||||
|
SuicColor suic_light_apply_shadows(SuicColor base, SuicVec3 normal, SuicVec3 worldPos,
|
||||||
|
SuicDirectionalLight* light);
|
||||||
|
|
||||||
|
/* Phong lighting (with specular) */
|
||||||
|
SuicColor suic_light_apply_phong(SuicColor base, SuicVec3 normal, SuicVec3 worldPos,
|
||||||
|
SuicVec3 camPos, SuicDirectionalLight* light);
|
||||||
|
|
||||||
|
/* Shadow calculations */
|
||||||
|
float suic_shadow_factor(SuicVec3 worldPos, SuicVec3 lightDir, float maxDist);
|
||||||
|
float suic_shadow_temporal(SuicVec3 worldPos, float timePhase);
|
||||||
|
|
||||||
|
/* Ambient occlusion */
|
||||||
|
float suic_ao_calculate(SuicVec3 worldPos, SuicVec3 normal);
|
||||||
BIN
game/suic_lighting.o
Normal file
BIN
game/suic_lighting.o
Normal file
Binary file not shown.
38
game/suic_math.c
Normal file
38
game/suic_math.c
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#include "suic_math.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
SuicVec3 suic_v3(float x, float y, float z) {
|
||||||
|
return (SuicVec3){x, y, z};
|
||||||
|
}
|
||||||
|
|
||||||
|
SuicVec3 suic_v3_add(SuicVec3 a, SuicVec3 b) {
|
||||||
|
return suic_v3(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
SuicVec3 suic_v3_sub(SuicVec3 a, SuicVec3 b) {
|
||||||
|
return suic_v3(a.x - b.x, a.y - b.y, a.z - b.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
SuicVec3 suic_v3_mul(SuicVec3 a, float s) {
|
||||||
|
return suic_v3(a.x * s, a.y * s, a.z * s);
|
||||||
|
}
|
||||||
|
|
||||||
|
float suic_v3_dot(SuicVec3 a, SuicVec3 b) {
|
||||||
|
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
float suic_v3_len(SuicVec3 a) {
|
||||||
|
return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
SuicVec3 suic_v3_norm(SuicVec3 a) {
|
||||||
|
float l = suic_v3_len(a);
|
||||||
|
if (l <= 1e-6f) return suic_v3(0, 0, 1);
|
||||||
|
return suic_v3(a.x / l, a.y / l, a.z / l);
|
||||||
|
}
|
||||||
|
|
||||||
|
SuicVec3 suic_v3_normalize(SuicVec3 v) {
|
||||||
|
float len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
|
||||||
|
if (len < 0.0001f) return suic_v3(0, 1, 0);
|
||||||
|
return suic_v3(v.x / len, v.y / len, v.z / len);
|
||||||
|
}
|
||||||
25
game/suic_math.h
Normal file
25
game/suic_math.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector3 math utilities for game code.
|
||||||
|
* Designed to be callable from .sui via transpiler bindings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct SuicVec3 {
|
||||||
|
float x, y, z;
|
||||||
|
} SuicVec3;
|
||||||
|
|
||||||
|
/* Construction */
|
||||||
|
SuicVec3 suic_v3(float x, float y, float z);
|
||||||
|
|
||||||
|
/* Arithmetic */
|
||||||
|
SuicVec3 suic_v3_add(SuicVec3 a, SuicVec3 b);
|
||||||
|
SuicVec3 suic_v3_sub(SuicVec3 a, SuicVec3 b);
|
||||||
|
SuicVec3 suic_v3_mul(SuicVec3 a, float s);
|
||||||
|
|
||||||
|
/* Dot/Length/Normalize */
|
||||||
|
float suic_v3_dot(SuicVec3 a, SuicVec3 b);
|
||||||
|
float suic_v3_len(SuicVec3 a);
|
||||||
|
SuicVec3 suic_v3_norm(SuicVec3 a);
|
||||||
|
SuicVec3 suic_v3_normalize(SuicVec3 v);
|
||||||
BIN
game/suic_math.o
Normal file
BIN
game/suic_math.o
Normal file
Binary file not shown.
337
game/suic_net.c
Normal file
337
game/suic_net.c
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
#include "suic_net.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
typedef int socklen_t;
|
||||||
|
#define CLOSESOCK closesocket
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#define CLOSESOCK close
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PROTOCOL_VERSION 67
|
||||||
|
|
||||||
|
/* ===== MESSAGE TYPES ===== */
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef enum MsgType : uint8_t {
|
||||||
|
MSG_HELLO = 1,
|
||||||
|
MSG_WELCOME = 2,
|
||||||
|
MSG_INPUT = 3,
|
||||||
|
MSG_SNAPSHOT = 4,
|
||||||
|
MSG_SHOOT = 5,
|
||||||
|
MSG_ITEMS = 6,
|
||||||
|
MSG_ROOM_STATE = 7
|
||||||
|
} MsgType;
|
||||||
|
|
||||||
|
typedef struct MsgHello {
|
||||||
|
uint8_t type;
|
||||||
|
uint32_t protocol;
|
||||||
|
char username[SUIC_NET_USERNAME_MAX];
|
||||||
|
} MsgHello;
|
||||||
|
|
||||||
|
typedef struct MsgWelcome {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t playerId;
|
||||||
|
uint32_t serverTick;
|
||||||
|
} MsgWelcome;
|
||||||
|
|
||||||
|
typedef struct MsgInput {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t playerId;
|
||||||
|
uint32_t clientTick;
|
||||||
|
float moveX, moveZ, yaw, pitch;
|
||||||
|
uint8_t buttons;
|
||||||
|
} MsgInput;
|
||||||
|
|
||||||
|
typedef struct MsgShoot {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t playerId;
|
||||||
|
uint32_t clientTick;
|
||||||
|
} MsgShoot;
|
||||||
|
|
||||||
|
typedef struct PlayerStateNet {
|
||||||
|
uint8_t id, alive;
|
||||||
|
int16_t hp;
|
||||||
|
float x, y, z, yaw, pitch;
|
||||||
|
uint8_t weapon;
|
||||||
|
int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits;
|
||||||
|
int16_t reloadTimeLeft;
|
||||||
|
char username[SUIC_NET_USERNAME_MAX];
|
||||||
|
} PlayerStateNet;
|
||||||
|
|
||||||
|
typedef struct MsgSnapshot {
|
||||||
|
uint8_t type;
|
||||||
|
uint32_t serverTick;
|
||||||
|
uint8_t count;
|
||||||
|
PlayerStateNet p[SUIC_NET_MAX_PLAYERS];
|
||||||
|
} MsgSnapshot;
|
||||||
|
|
||||||
|
typedef struct ItemNet {
|
||||||
|
uint16_t id;
|
||||||
|
uint8_t type;
|
||||||
|
int16_t qty;
|
||||||
|
float x, y, z;
|
||||||
|
} ItemNet;
|
||||||
|
|
||||||
|
typedef struct MsgItems {
|
||||||
|
uint8_t type;
|
||||||
|
uint32_t serverTick;
|
||||||
|
uint8_t count;
|
||||||
|
ItemNet items[SUIC_NET_MAX_ITEMS];
|
||||||
|
} MsgItems;
|
||||||
|
|
||||||
|
typedef struct MsgRoomState {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t state;
|
||||||
|
float countdownRemaining;
|
||||||
|
uint8_t winnerId;
|
||||||
|
char winnerName[SUIC_NET_USERNAME_MAX];
|
||||||
|
} MsgRoomState;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
/* ===== LOCAL PLAYER DATA ===== */
|
||||||
|
|
||||||
|
typedef struct RemotePlayer {
|
||||||
|
int present, alive, hp;
|
||||||
|
float x, y, z;
|
||||||
|
float prevX, prevY, prevZ;
|
||||||
|
float yaw, pitch;
|
||||||
|
uint8_t weapon;
|
||||||
|
int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits;
|
||||||
|
int16_t reloadTimeLeft;
|
||||||
|
char username[SUIC_NET_USERNAME_MAX];
|
||||||
|
} RemotePlayer;
|
||||||
|
|
||||||
|
typedef struct WorldItem {
|
||||||
|
int present;
|
||||||
|
uint16_t id;
|
||||||
|
uint8_t type;
|
||||||
|
int16_t qty;
|
||||||
|
float x, y, z;
|
||||||
|
} WorldItem;
|
||||||
|
|
||||||
|
/* ===== STATIC STATE ===== */
|
||||||
|
|
||||||
|
static int g_sock = -1;
|
||||||
|
static struct sockaddr_in g_server = {0};
|
||||||
|
static uint8_t g_myId = 255;
|
||||||
|
static RemotePlayer g_players[SUIC_NET_MAX_PLAYERS] = {0};
|
||||||
|
static WorldItem g_items[SUIC_NET_MAX_ITEMS] = {0};
|
||||||
|
static int g_roomState = 0;
|
||||||
|
static float g_countdown = 0.0f;
|
||||||
|
static char g_winnerName[SUIC_NET_USERNAME_MAX] = {0};
|
||||||
|
|
||||||
|
static void set_nonblocking(int sock) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
u_long mode = 1;
|
||||||
|
ioctlsocket(sock, FIONBIO, &mode);
|
||||||
|
#else
|
||||||
|
int flags = fcntl(sock, F_GETFL, 0);
|
||||||
|
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== PUBLIC API ===== */
|
||||||
|
|
||||||
|
int suic_net_init(const char* server_ip, int port) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSADATA wsa;
|
||||||
|
WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
g_sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
if (g_sock < 0) {
|
||||||
|
perror("socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
set_nonblocking(g_sock);
|
||||||
|
|
||||||
|
memset(&g_server, 0, sizeof(g_server));
|
||||||
|
g_server.sin_family = AF_INET;
|
||||||
|
g_server.sin_port = htons(port);
|
||||||
|
inet_pton(AF_INET, server_ip, &g_server.sin_addr);
|
||||||
|
|
||||||
|
g_myId = 255;
|
||||||
|
memset(g_players, 0, sizeof(g_players));
|
||||||
|
memset(g_items, 0, sizeof(g_items));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_net_shutdown(void) {
|
||||||
|
if (g_sock >= 0) {
|
||||||
|
CLOSESOCK(g_sock);
|
||||||
|
g_sock = -1;
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSACleanup();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool suic_net_is_connected(void) {
|
||||||
|
return g_myId != 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_net_send_hello(const char* username) {
|
||||||
|
MsgHello hello = {0};
|
||||||
|
hello.type = MSG_HELLO;
|
||||||
|
hello.protocol = PROTOCOL_VERSION;
|
||||||
|
strncpy(hello.username, username, SUIC_NET_USERNAME_MAX - 1);
|
||||||
|
sendto(g_sock, (const char*)&hello, (int)sizeof(hello), 0,
|
||||||
|
(const struct sockaddr*)&g_server, sizeof(g_server));
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_net_send_input(uint8_t playerId, uint32_t clientTick,
|
||||||
|
float moveX, float moveZ, float yaw, float pitch,
|
||||||
|
uint8_t buttons) {
|
||||||
|
MsgInput in = {0};
|
||||||
|
in.type = MSG_INPUT;
|
||||||
|
in.playerId = playerId;
|
||||||
|
in.clientTick = clientTick;
|
||||||
|
in.moveX = moveX;
|
||||||
|
in.moveZ = moveZ;
|
||||||
|
in.yaw = yaw;
|
||||||
|
in.pitch = pitch;
|
||||||
|
in.buttons = buttons;
|
||||||
|
sendto(g_sock, (const char*)&in, (int)sizeof(in), 0,
|
||||||
|
(const struct sockaddr*)&g_server, sizeof(g_server));
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_net_send_shoot(uint8_t playerId, uint32_t clientTick) {
|
||||||
|
MsgShoot msg = {0};
|
||||||
|
msg.type = MSG_SHOOT;
|
||||||
|
msg.playerId = playerId;
|
||||||
|
msg.clientTick = clientTick;
|
||||||
|
sendto(g_sock, (const char*)&msg, (int)sizeof(msg), 0,
|
||||||
|
(const struct sockaddr*)&g_server, sizeof(g_server));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool suic_net_poll(void) {
|
||||||
|
bool receivedAny = false;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
uint8_t buf[1400];
|
||||||
|
struct sockaddr_in from = {0};
|
||||||
|
socklen_t fromLen = sizeof(from);
|
||||||
|
int n = (int)recvfrom(g_sock, (char*)buf, (int)sizeof(buf), 0,
|
||||||
|
(struct sockaddr*)&from, &fromLen);
|
||||||
|
if (n <= 0) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (WSAGetLastError() == WSAEWOULDBLOCK) break;
|
||||||
|
#else
|
||||||
|
if (errno == EWOULDBLOCK || errno == EAGAIN) break;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedAny = true;
|
||||||
|
uint8_t type = buf[0];
|
||||||
|
|
||||||
|
if (type == MSG_WELCOME && n >= (int)sizeof(MsgWelcome)) {
|
||||||
|
MsgWelcome* w = (MsgWelcome*)buf;
|
||||||
|
g_myId = w->playerId;
|
||||||
|
} else if (type == MSG_SNAPSHOT && n >= (int)sizeof(MsgSnapshot)) {
|
||||||
|
MsgSnapshot* s = (MsgSnapshot*)buf;
|
||||||
|
for (int i = 0; i < SUIC_NET_MAX_PLAYERS; i++)
|
||||||
|
g_players[i].present = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)s->count && i < SUIC_NET_MAX_PLAYERS; i++) {
|
||||||
|
PlayerStateNet* ps = &s->p[i];
|
||||||
|
if (ps->id >= SUIC_NET_MAX_PLAYERS) continue;
|
||||||
|
|
||||||
|
RemotePlayer* p = &g_players[ps->id];
|
||||||
|
p->prevX = p->x;
|
||||||
|
p->prevY = p->y;
|
||||||
|
p->prevZ = p->z;
|
||||||
|
p->present = 1;
|
||||||
|
p->alive = ps->alive;
|
||||||
|
p->hp = ps->hp;
|
||||||
|
p->x = ps->x;
|
||||||
|
p->y = ps->y;
|
||||||
|
p->z = ps->z;
|
||||||
|
p->yaw = ps->yaw;
|
||||||
|
p->pitch = ps->pitch;
|
||||||
|
p->weapon = ps->weapon;
|
||||||
|
p->pistolMag = ps->pistolMag;
|
||||||
|
p->rifleMag = ps->rifleMag;
|
||||||
|
p->pistolAmmo = ps->pistolAmmo;
|
||||||
|
p->rifleAmmo = ps->rifleAmmo;
|
||||||
|
p->medkits = ps->medkits;
|
||||||
|
p->reloadTimeLeft = ps->reloadTimeLeft;
|
||||||
|
memset(p->username, 0, SUIC_NET_USERNAME_MAX);
|
||||||
|
strncpy(p->username, ps->username, SUIC_NET_USERNAME_MAX - 1);
|
||||||
|
}
|
||||||
|
} else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) {
|
||||||
|
MsgItems* m = (MsgItems*)buf;
|
||||||
|
for (int i = 0; i < SUIC_NET_MAX_ITEMS; i++)
|
||||||
|
g_items[i].present = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)m->count && i < SUIC_NET_MAX_ITEMS; i++) {
|
||||||
|
g_items[i].present = 1;
|
||||||
|
g_items[i].id = m->items[i].id;
|
||||||
|
g_items[i].type = m->items[i].type;
|
||||||
|
g_items[i].qty = m->items[i].qty;
|
||||||
|
g_items[i].x = m->items[i].x;
|
||||||
|
g_items[i].y = m->items[i].y;
|
||||||
|
g_items[i].z = m->items[i].z;
|
||||||
|
}
|
||||||
|
} else if (type == MSG_ROOM_STATE && n >= (int)sizeof(MsgRoomState)) {
|
||||||
|
const MsgRoomState* rs = (const MsgRoomState*)buf;
|
||||||
|
g_roomState = rs->state;
|
||||||
|
g_countdown = rs->countdownRemaining;
|
||||||
|
memset(g_winnerName, 0, SUIC_NET_USERNAME_MAX);
|
||||||
|
if (rs->winnerId < 255) {
|
||||||
|
strncpy(g_winnerName, rs->winnerName, SUIC_NET_USERNAME_MAX - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return receivedAny;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t suic_net_get_my_id(void) { return g_myId; }
|
||||||
|
|
||||||
|
/* Player accessors */
|
||||||
|
bool suic_net_player_present(int i) { return i >= 0 && i < SUIC_NET_MAX_PLAYERS && g_players[i].present; }
|
||||||
|
bool suic_net_player_alive(int i) { return suic_net_player_present(i) && g_players[i].alive; }
|
||||||
|
int suic_net_player_hp(int i) { return suic_net_player_present(i) ? g_players[i].hp : 0; }
|
||||||
|
float suic_net_player_x(int i) { return suic_net_player_present(i) ? g_players[i].x : 0; }
|
||||||
|
float suic_net_player_y(int i) { return suic_net_player_present(i) ? g_players[i].y : 0; }
|
||||||
|
float suic_net_player_z(int i) { return suic_net_player_present(i) ? g_players[i].z : 0; }
|
||||||
|
float suic_net_player_prev_x(int i) { return suic_net_player_present(i) ? g_players[i].prevX : 0; }
|
||||||
|
float suic_net_player_prev_y(int i) { return suic_net_player_present(i) ? g_players[i].prevY : 0; }
|
||||||
|
float suic_net_player_prev_z(int i) { return suic_net_player_present(i) ? g_players[i].prevZ : 0; }
|
||||||
|
float suic_net_player_yaw(int i) { return suic_net_player_present(i) ? g_players[i].yaw : 0; }
|
||||||
|
float suic_net_player_pitch(int i) { return suic_net_player_present(i) ? g_players[i].pitch : 0; }
|
||||||
|
uint8_t suic_net_player_weapon(int i) { return suic_net_player_present(i) ? g_players[i].weapon : 0; }
|
||||||
|
int16_t suic_net_player_pistol_mag(int i) { return suic_net_player_present(i) ? g_players[i].pistolMag : 0; }
|
||||||
|
int16_t suic_net_player_rifle_mag(int i) { return suic_net_player_present(i) ? g_players[i].rifleMag : 0; }
|
||||||
|
int16_t suic_net_player_pistol_ammo(int i) { return suic_net_player_present(i) ? g_players[i].pistolAmmo : 0; }
|
||||||
|
int16_t suic_net_player_rifle_ammo(int i) { return suic_net_player_present(i) ? g_players[i].rifleAmmo : 0; }
|
||||||
|
int16_t suic_net_player_medkits(int i) { return suic_net_player_present(i) ? g_players[i].medkits : 0; }
|
||||||
|
int16_t suic_net_player_reload_time(int i) { return suic_net_player_present(i) ? g_players[i].reloadTimeLeft : 0; }
|
||||||
|
const char* suic_net_player_username(int i) { return suic_net_player_present(i) ? g_players[i].username : ""; }
|
||||||
|
|
||||||
|
/* Item accessors */
|
||||||
|
bool suic_net_item_present(int i) { return i >= 0 && i < SUIC_NET_MAX_ITEMS && g_items[i].present; }
|
||||||
|
uint16_t suic_net_item_id(int i) { return suic_net_item_present(i) ? g_items[i].id : 0; }
|
||||||
|
uint8_t suic_net_item_type(int i) { return suic_net_item_present(i) ? g_items[i].type : 0; }
|
||||||
|
int16_t suic_net_item_qty(int i) { return suic_net_item_present(i) ? g_items[i].qty : 0; }
|
||||||
|
float suic_net_item_x(int i) { return suic_net_item_present(i) ? g_items[i].x : 0; }
|
||||||
|
float suic_net_item_y(int i) { return suic_net_item_present(i) ? g_items[i].y : 0; }
|
||||||
|
float suic_net_item_z(int i) { return suic_net_item_present(i) ? g_items[i].z : 0; }
|
||||||
|
|
||||||
|
/* Room state */
|
||||||
|
int suic_net_room_state(void) { return g_roomState; }
|
||||||
|
float suic_net_countdown(void) { return g_countdown; }
|
||||||
|
const char* suic_net_winner_name(void) { return g_winnerName; }
|
||||||
81
game/suic_net.h
Normal file
81
game/suic_net.h
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network protocol API for game client.
|
||||||
|
* Abstracts UDP socket communication with the game server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define SUIC_NET_MAX_PLAYERS 16
|
||||||
|
#define SUIC_NET_USERNAME_MAX 16
|
||||||
|
#define SUIC_NET_MAX_ITEMS 64
|
||||||
|
|
||||||
|
/* Connection lifecycle */
|
||||||
|
int suic_net_init(const char* server_ip, int port);
|
||||||
|
void suic_net_shutdown(void);
|
||||||
|
bool suic_net_is_connected(void);
|
||||||
|
|
||||||
|
/* Sending */
|
||||||
|
void suic_net_send_hello(const char* username);
|
||||||
|
void suic_net_send_input(uint8_t playerId, uint32_t clientTick,
|
||||||
|
float moveX, float moveZ, float yaw, float pitch,
|
||||||
|
uint8_t buttons);
|
||||||
|
void suic_net_send_shoot(uint8_t playerId, uint32_t clientTick);
|
||||||
|
|
||||||
|
/* Receiving (poll-based) */
|
||||||
|
bool suic_net_poll(void);
|
||||||
|
uint8_t suic_net_get_my_id(void);
|
||||||
|
|
||||||
|
/* Player accessors */
|
||||||
|
bool suic_net_player_present(int index);
|
||||||
|
bool suic_net_player_alive(int index);
|
||||||
|
int suic_net_player_hp(int index);
|
||||||
|
float suic_net_player_x(int index);
|
||||||
|
float suic_net_player_y(int index);
|
||||||
|
float suic_net_player_z(int index);
|
||||||
|
float suic_net_player_prev_x(int index);
|
||||||
|
float suic_net_player_prev_y(int index);
|
||||||
|
float suic_net_player_prev_z(int index);
|
||||||
|
float suic_net_player_yaw(int index);
|
||||||
|
float suic_net_player_pitch(int index);
|
||||||
|
uint8_t suic_net_player_weapon(int index);
|
||||||
|
int16_t suic_net_player_pistol_mag(int index);
|
||||||
|
int16_t suic_net_player_rifle_mag(int index);
|
||||||
|
int16_t suic_net_player_pistol_ammo(int index);
|
||||||
|
int16_t suic_net_player_rifle_ammo(int index);
|
||||||
|
int16_t suic_net_player_medkits(int index);
|
||||||
|
int16_t suic_net_player_reload_time(int index);
|
||||||
|
const char* suic_net_player_username(int index);
|
||||||
|
|
||||||
|
/* Item accessors */
|
||||||
|
bool suic_net_item_present(int index);
|
||||||
|
uint16_t suic_net_item_id(int index);
|
||||||
|
uint8_t suic_net_item_type(int index);
|
||||||
|
int16_t suic_net_item_qty(int index);
|
||||||
|
float suic_net_item_x(int index);
|
||||||
|
float suic_net_item_y(int index);
|
||||||
|
float suic_net_item_z(int index);
|
||||||
|
|
||||||
|
/* Room state */
|
||||||
|
int suic_net_room_state(void);
|
||||||
|
float suic_net_countdown(void);
|
||||||
|
const char* suic_net_winner_name(void);
|
||||||
|
|
||||||
|
/* Button flags */
|
||||||
|
#define SUIC_BTN_RELOAD (1u << 0)
|
||||||
|
#define SUIC_BTN_SWITCH_PISTOL (1u << 1)
|
||||||
|
#define SUIC_BTN_SWITCH_RIFLE (1u << 2)
|
||||||
|
#define SUIC_BTN_PICK (1u << 3)
|
||||||
|
#define SUIC_BTN_USE_MEDKIT (1u << 4)
|
||||||
|
#define SUIC_BTN_JUMP (1u << 5)
|
||||||
|
|
||||||
|
/* Weapon types */
|
||||||
|
#define SUIC_WEAPON_PISTOL 0
|
||||||
|
#define SUIC_WEAPON_RIFLE 1
|
||||||
|
|
||||||
|
/* Item types */
|
||||||
|
#define SUIC_ITEM_NONE 0
|
||||||
|
#define SUIC_ITEM_MEDKIT 1
|
||||||
|
#define SUIC_ITEM_AMMO_PISTOL 2
|
||||||
|
#define SUIC_ITEM_AMMO_RIFLE 3
|
||||||
BIN
game/suic_net.o
Normal file
BIN
game/suic_net.o
Normal file
Binary file not shown.
283
game/suic_terrain.c
Normal file
283
game/suic_terrain.c
Normal file
|
|
@ -0,0 +1,283 @@
|
||||||
|
#include "suic_terrain.h"
|
||||||
|
#include "raylib.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* ===== SIMPLEX NOISE IMPLEMENTATION ===== */
|
||||||
|
|
||||||
|
static const uint8_t perm[512] = {
|
||||||
|
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7,
|
||||||
|
225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190,
|
||||||
|
6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117,
|
||||||
|
35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136,
|
||||||
|
171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158,
|
||||||
|
231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46,
|
||||||
|
245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209,
|
||||||
|
76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86,
|
||||||
|
164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5,
|
||||||
|
202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16,
|
||||||
|
58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44,
|
||||||
|
154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253,
|
||||||
|
19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97,
|
||||||
|
228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51,
|
||||||
|
145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184,
|
||||||
|
84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,
|
||||||
|
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156,
|
||||||
|
180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233,
|
||||||
|
7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
|
||||||
|
190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203,
|
||||||
|
117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125,
|
||||||
|
136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146,
|
||||||
|
158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55,
|
||||||
|
46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73,
|
||||||
|
209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159,
|
||||||
|
86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
|
||||||
|
5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47,
|
||||||
|
16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2,
|
||||||
|
44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39,
|
||||||
|
253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246,
|
||||||
|
97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81,
|
||||||
|
51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
|
||||||
|
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205,
|
||||||
|
93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61,
|
||||||
|
156, 180};
|
||||||
|
|
||||||
|
static float grad2(int hash, float x, float y) {
|
||||||
|
int h = hash & 7;
|
||||||
|
float u = h < 4 ? x : y;
|
||||||
|
float v = h < 4 ? y : x;
|
||||||
|
return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f * v : 2.0f * v);
|
||||||
|
}
|
||||||
|
|
||||||
|
float suic_simplex_noise_2d(float x, float y) {
|
||||||
|
const float F2 = 0.366025403f;
|
||||||
|
const float G2 = 0.211324865f;
|
||||||
|
float s = (x + y) * F2;
|
||||||
|
int i = (int)floorf(x + s);
|
||||||
|
int j = (int)floorf(y + s);
|
||||||
|
float t = (i + j) * G2;
|
||||||
|
float X0 = i - t, Y0 = j - t;
|
||||||
|
float x0 = x - X0, y0 = y - Y0;
|
||||||
|
int i1 = (x0 > y0) ? 1 : 0;
|
||||||
|
int j1 = (x0 > y0) ? 0 : 1;
|
||||||
|
float x1 = x0 - i1 + G2, y1 = y0 - j1 + G2;
|
||||||
|
float x2 = x0 - 1.0f + 2.0f * G2, y2 = y0 - 1.0f + 2.0f * G2;
|
||||||
|
int ii = i & 255, jj = j & 255;
|
||||||
|
float n0 = 0.0f, n1 = 0.0f, n2 = 0.0f;
|
||||||
|
float t0 = 0.5f - x0 * x0 - y0 * y0;
|
||||||
|
if (t0 >= 0.0f) {
|
||||||
|
t0 *= t0;
|
||||||
|
n0 = t0 * t0 * grad2(perm[ii + perm[jj]], x0, y0);
|
||||||
|
}
|
||||||
|
float t1 = 0.5f - x1 * x1 - y1 * y1;
|
||||||
|
if (t1 >= 0.0f) {
|
||||||
|
t1 *= t1;
|
||||||
|
n1 = t1 * t1 * grad2(perm[ii + i1 + perm[jj + j1]], x1, y1);
|
||||||
|
}
|
||||||
|
float t2 = 0.5f - x2 * x2 - y2 * y2;
|
||||||
|
if (t2 >= 0.0f) {
|
||||||
|
t2 *= t2;
|
||||||
|
n2 = t2 * t2 * grad2(perm[ii + 1 + perm[jj + 1]], x2, y2);
|
||||||
|
}
|
||||||
|
return 45.0f * (n0 + n1 + n2);
|
||||||
|
}
|
||||||
|
|
||||||
|
float suic_fbm_noise(float x, float y, int octaves) {
|
||||||
|
float value = 0.0f, amplitude = 1.0f, frequency = 1.0f, max_value = 0.0f;
|
||||||
|
for (int i = 0; i < octaves; i++) {
|
||||||
|
value += suic_simplex_noise_2d(x * frequency, y * frequency) * amplitude;
|
||||||
|
max_value += amplitude;
|
||||||
|
amplitude *= 0.5f;
|
||||||
|
frequency *= 2.0f;
|
||||||
|
}
|
||||||
|
return value / max_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float suic_terrain_height(float x, float z) {
|
||||||
|
float height = suic_fbm_noise(x * 0.05f, z * 0.05f, 2);
|
||||||
|
height += suic_fbm_noise(x * 0.01f, z * 0.01f, 2) * 1.5f;
|
||||||
|
return height * 4.0f + 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== TERRAIN MESH & MODEL ===== */
|
||||||
|
|
||||||
|
static Mesh g_terrainMesh = {0};
|
||||||
|
static Model g_terrainModel = {0};
|
||||||
|
static SuicTerrainShader g_terrainShader = {0};
|
||||||
|
static bool g_terrainInitialized = false;
|
||||||
|
|
||||||
|
static Mesh generate_terrain_mesh(void) {
|
||||||
|
int size = SUIC_TERRAIN_SIZE;
|
||||||
|
Mesh mesh = {0};
|
||||||
|
|
||||||
|
int vertexCount = size * size;
|
||||||
|
int triangleCount = (size - 1) * (size - 1) * 2;
|
||||||
|
|
||||||
|
mesh.vertexCount = vertexCount;
|
||||||
|
mesh.triangleCount = triangleCount;
|
||||||
|
|
||||||
|
mesh.vertices = (float *)MemAlloc(vertexCount * 3 * sizeof(float));
|
||||||
|
mesh.texcoords = (float *)MemAlloc(vertexCount * 2 * sizeof(float));
|
||||||
|
mesh.normals = (float *)MemAlloc(vertexCount * 3 * sizeof(float));
|
||||||
|
mesh.indices = (unsigned short *)MemAlloc(triangleCount * 3 * sizeof(unsigned short));
|
||||||
|
|
||||||
|
/* Generate vertices */
|
||||||
|
for (int z = 0; z < size; z++) {
|
||||||
|
for (int x = 0; x < size; x++) {
|
||||||
|
int idx = z * size + x;
|
||||||
|
float wx = ((float)x - size / 2.0f) * SUIC_TERRAIN_SCALE;
|
||||||
|
float wz = ((float)z - size / 2.0f) * SUIC_TERRAIN_SCALE;
|
||||||
|
float wy = suic_terrain_height(wx, wz);
|
||||||
|
|
||||||
|
mesh.vertices[idx * 3 + 0] = wx;
|
||||||
|
mesh.vertices[idx * 3 + 1] = wy;
|
||||||
|
mesh.vertices[idx * 3 + 2] = wz;
|
||||||
|
|
||||||
|
mesh.texcoords[idx * 2 + 0] = (float)x / (float)(size - 1);
|
||||||
|
mesh.texcoords[idx * 2 + 1] = (float)z / (float)(size - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate indices */
|
||||||
|
int triIdx = 0;
|
||||||
|
for (int z = 0; z < size - 1; z++) {
|
||||||
|
for (int x = 0; x < size - 1; x++) {
|
||||||
|
int i0 = z * size + x;
|
||||||
|
int i1 = z * size + (x + 1);
|
||||||
|
int i2 = (z + 1) * size + x;
|
||||||
|
int i3 = (z + 1) * size + (x + 1);
|
||||||
|
|
||||||
|
mesh.indices[triIdx * 3 + 0] = i0;
|
||||||
|
mesh.indices[triIdx * 3 + 1] = i2;
|
||||||
|
mesh.indices[triIdx * 3 + 2] = i1;
|
||||||
|
triIdx++;
|
||||||
|
|
||||||
|
mesh.indices[triIdx * 3 + 0] = i1;
|
||||||
|
mesh.indices[triIdx * 3 + 1] = i2;
|
||||||
|
mesh.indices[triIdx * 3 + 2] = i3;
|
||||||
|
triIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate normals using height field gradient */
|
||||||
|
for (int z = 0; z < size; z++) {
|
||||||
|
for (int x = 0; x < size; x++) {
|
||||||
|
int idx = z * size + x;
|
||||||
|
|
||||||
|
float h_right = (x + 1 < size)
|
||||||
|
? mesh.vertices[(z * size + (x + 1)) * 3 + 1]
|
||||||
|
: mesh.vertices[idx * 3 + 1];
|
||||||
|
float h_left = (x - 1 >= 0) ? mesh.vertices[(z * size + (x - 1)) * 3 + 1]
|
||||||
|
: mesh.vertices[idx * 3 + 1];
|
||||||
|
float h_down = (z + 1 < size)
|
||||||
|
? mesh.vertices[((z + 1) * size + x) * 3 + 1]
|
||||||
|
: mesh.vertices[idx * 3 + 1];
|
||||||
|
float h_up = (z - 1 >= 0) ? mesh.vertices[((z - 1) * size + x) * 3 + 1]
|
||||||
|
: mesh.vertices[idx * 3 + 1];
|
||||||
|
|
||||||
|
float dh_dx = (h_right - h_left) / (2.0f * SUIC_TERRAIN_SCALE);
|
||||||
|
float dh_dz = (h_down - h_up) / (2.0f * SUIC_TERRAIN_SCALE);
|
||||||
|
|
||||||
|
float nx = -dh_dx;
|
||||||
|
float ny = 1.0f;
|
||||||
|
float nz = -dh_dz;
|
||||||
|
|
||||||
|
float len = sqrtf(nx * nx + ny * ny + nz * nz);
|
||||||
|
if (len > 0.0001f) {
|
||||||
|
mesh.normals[idx * 3 + 0] = nx / len;
|
||||||
|
mesh.normals[idx * 3 + 1] = ny / len;
|
||||||
|
mesh.normals[idx * 3 + 2] = nz / len;
|
||||||
|
} else {
|
||||||
|
mesh.normals[idx * 3 + 0] = 0;
|
||||||
|
mesh.normals[idx * 3 + 1] = 1;
|
||||||
|
mesh.normals[idx * 3 + 2] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UploadMesh(&mesh, false);
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_terrain_init(void) {
|
||||||
|
if (g_terrainInitialized) return;
|
||||||
|
|
||||||
|
g_terrainMesh = generate_terrain_mesh();
|
||||||
|
g_terrainModel = LoadModelFromMesh(g_terrainMesh);
|
||||||
|
g_terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = (Color){80, 140, 70, 255};
|
||||||
|
|
||||||
|
g_terrainShader = suic_terrain_shader_load();
|
||||||
|
if (g_terrainShader.shaderId != 0) {
|
||||||
|
g_terrainModel.materials[0].shader.id = g_terrainShader.shaderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_terrainInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_terrain_draw(SuicVec3 cameraPos) {
|
||||||
|
if (!g_terrainInitialized) return;
|
||||||
|
|
||||||
|
/* Set shader uniforms if shader is loaded */
|
||||||
|
if (g_terrainShader.shaderId != 0) {
|
||||||
|
SuicVec3 lightDir = suic_v3_norm(suic_v3(-0.8f, -1.0f, -0.6f));
|
||||||
|
SuicVec3 lightColor = suic_v3(1.0f, 1.0f, 1.0f);
|
||||||
|
suic_terrain_shader_set_uniforms(&g_terrainShader, cameraPos, lightDir, lightColor, 1.2f, 0.3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color = (Color){80, 140, 70, 255};
|
||||||
|
DrawModel(g_terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_terrain_cleanup(void) {
|
||||||
|
if (!g_terrainInitialized) return;
|
||||||
|
UnloadModel(g_terrainModel);
|
||||||
|
suic_terrain_shader_unload(&g_terrainShader);
|
||||||
|
g_terrainInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== SHADER MANAGEMENT ===== */
|
||||||
|
|
||||||
|
SuicTerrainShader suic_terrain_shader_load(void) {
|
||||||
|
SuicTerrainShader ts = {0};
|
||||||
|
Shader shader = LoadShader("terrain.vs", "terrain.fs");
|
||||||
|
ts.shaderId = shader.id;
|
||||||
|
|
||||||
|
if (shader.id != 0) {
|
||||||
|
ts.locViewPos = GetShaderLocation(shader, "viewPos");
|
||||||
|
ts.locLightDir = GetShaderLocation(shader, "lightDir");
|
||||||
|
ts.locLightColor = GetShaderLocation(shader, "lightColor");
|
||||||
|
ts.locLightIntensity = GetShaderLocation(shader, "lightIntensity");
|
||||||
|
ts.locAmbientIntensity = GetShaderLocation(shader, "ambientIntensity");
|
||||||
|
ts.locTerrainColor = GetShaderLocation(shader, "terrainColor");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_terrain_shader_unload(SuicTerrainShader* ts) {
|
||||||
|
if (ts && ts->shaderId != 0) {
|
||||||
|
Shader s = {ts->shaderId, NULL};
|
||||||
|
UnloadShader(s);
|
||||||
|
ts->shaderId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void suic_terrain_shader_set_uniforms(SuicTerrainShader* ts, SuicVec3 viewPos,
|
||||||
|
SuicVec3 lightDir, SuicVec3 lightColor,
|
||||||
|
float intensity, float ambient) {
|
||||||
|
if (!ts || ts->shaderId == 0) return;
|
||||||
|
|
||||||
|
Shader shader = {ts->shaderId, NULL};
|
||||||
|
float viewPosArray[3] = {viewPos.x, viewPos.y, viewPos.z};
|
||||||
|
float lightDirArray[3] = {-lightDir.x, -lightDir.y, -lightDir.z};
|
||||||
|
float lightColorArray[3] = {lightColor.x, lightColor.y, lightColor.z};
|
||||||
|
float terrainColorArray[3] = {80.0f / 255.0f, 140.0f / 255.0f, 70.0f / 255.0f};
|
||||||
|
|
||||||
|
SetShaderValue(shader, ts->locViewPos, viewPosArray, SHADER_UNIFORM_VEC3);
|
||||||
|
SetShaderValue(shader, ts->locLightDir, lightDirArray, SHADER_UNIFORM_VEC3);
|
||||||
|
SetShaderValue(shader, ts->locLightColor, lightColorArray, SHADER_UNIFORM_VEC3);
|
||||||
|
SetShaderValue(shader, ts->locLightIntensity, &intensity, SHADER_UNIFORM_FLOAT);
|
||||||
|
SetShaderValue(shader, ts->locAmbientIntensity, &ambient, SHADER_UNIFORM_FLOAT);
|
||||||
|
SetShaderValue(shader, ts->locTerrainColor, terrainColorArray, SHADER_UNIFORM_VEC3);
|
||||||
|
}
|
||||||
43
game/suic_terrain.h
Normal file
43
game/suic_terrain.h
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
#include "suic_math.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terrain generation and rendering API.
|
||||||
|
* Uses simplex noise for procedural heightmap generation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Terrain constants */
|
||||||
|
#define SUIC_TERRAIN_SIZE 256
|
||||||
|
#define SUIC_TERRAIN_SCALE 1.0f
|
||||||
|
#define SUIC_TERRAIN_MIN (-SUIC_TERRAIN_SIZE * SUIC_TERRAIN_SCALE / 2.0f)
|
||||||
|
#define SUIC_TERRAIN_MAX (SUIC_TERRAIN_SIZE * SUIC_TERRAIN_SCALE / 2.0f)
|
||||||
|
|
||||||
|
/* Noise generation */
|
||||||
|
float suic_simplex_noise_2d(float x, float y);
|
||||||
|
float suic_fbm_noise(float x, float y, int octaves);
|
||||||
|
|
||||||
|
/* Terrain height query */
|
||||||
|
float suic_terrain_height(float x, float z);
|
||||||
|
|
||||||
|
/* Terrain lifecycle */
|
||||||
|
void suic_terrain_init(void);
|
||||||
|
void suic_terrain_draw(SuicVec3 cameraPos);
|
||||||
|
void suic_terrain_cleanup(void);
|
||||||
|
|
||||||
|
/* Shader management */
|
||||||
|
typedef struct SuicTerrainShader {
|
||||||
|
unsigned int shaderId;
|
||||||
|
int locViewPos;
|
||||||
|
int locLightDir;
|
||||||
|
int locLightColor;
|
||||||
|
int locLightIntensity;
|
||||||
|
int locAmbientIntensity;
|
||||||
|
int locTerrainColor;
|
||||||
|
} SuicTerrainShader;
|
||||||
|
|
||||||
|
SuicTerrainShader suic_terrain_shader_load(void);
|
||||||
|
void suic_terrain_shader_unload(SuicTerrainShader* ts);
|
||||||
|
void suic_terrain_shader_set_uniforms(SuicTerrainShader* ts, SuicVec3 viewPos,
|
||||||
|
SuicVec3 lightDir, SuicVec3 lightColor,
|
||||||
|
float intensity, float ambient);
|
||||||
BIN
game/suic_terrain.o
Normal file
BIN
game/suic_terrain.o
Normal file
Binary file not shown.
163
game/suic_ui.c
Normal file
163
game/suic_ui.c
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
#include "suic_ui.h"
|
||||||
|
#include "suic_net.h"
|
||||||
|
#include "raylib.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
/* ===== USERNAME PROMPT ===== */
|
||||||
|
|
||||||
|
void suic_ui_username_prompt(char* outName, int maxLen) {
|
||||||
|
memset(outName, 0, maxLen);
|
||||||
|
while (!WindowShouldClose()) {
|
||||||
|
int ch = GetCharPressed();
|
||||||
|
while (ch > 0) {
|
||||||
|
int len = (int)strlen(outName);
|
||||||
|
if (ch >= 32 && ch <= 126) {
|
||||||
|
if (len < maxLen - 1) {
|
||||||
|
outName[len] = (char)ch;
|
||||||
|
outName[len + 1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch = GetCharPressed();
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_BACKSPACE)) {
|
||||||
|
int len = (int)strlen(outName);
|
||||||
|
if (len > 0) outName[len - 1] = '\0';
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_ENTER) && strlen(outName) > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BeginDrawing();
|
||||||
|
ClearBackground((Color){20, 24, 32, 255});
|
||||||
|
DrawText("Enter username (press ENTER):", 60, 80, 28, RAYWHITE);
|
||||||
|
DrawRectangle(60, 130, 420, 48, (Color){40, 48, 64, 255});
|
||||||
|
DrawRectangleLines(60, 130, 420, 48, (Color){120, 140, 170, 255});
|
||||||
|
DrawText(outName[0] ? outName : "_", 72, 142, 24, (Color){230, 230, 240, 255});
|
||||||
|
EndDrawing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== HUD ===== */
|
||||||
|
|
||||||
|
void suic_ui_draw_hud(int hp, int weapon, int mag, int reserve,
|
||||||
|
int medkits, int reloadTimeLeft) {
|
||||||
|
int sw = GetScreenWidth();
|
||||||
|
int sh = GetScreenHeight();
|
||||||
|
|
||||||
|
/* Health panel */
|
||||||
|
DrawRectangle(10, 10, 280, 110, (Color){0, 0, 0, 120});
|
||||||
|
DrawText("HP", 20, 20, 20, RAYWHITE);
|
||||||
|
int healthBarWidth = (hp * 220) / 100;
|
||||||
|
Color healthColor = (hp > 60) ? (Color){80, 255, 80, 120}
|
||||||
|
: (hp > 30) ? (Color){255, 200, 80, 120}
|
||||||
|
: (Color){255, 80, 80, 120};
|
||||||
|
DrawRectangle(20, 45, 220, 20, (Color){40, 40, 40, 255});
|
||||||
|
DrawRectangle(20, 45, healthBarWidth, 20, healthColor);
|
||||||
|
DrawText(TextFormat("%d", hp), 250, 47, 18, RAYWHITE);
|
||||||
|
DrawText(TextFormat("Medkits: %d (H to use)", medkits), 20, 75, 18,
|
||||||
|
(medkits > 0) ? (Color){120, 255, 120, 120} : (Color){120, 120, 120, 120});
|
||||||
|
DrawText("1=Pistol 2=Rifle R=Reload F=Pick SPACE=Jump", 20, 95, 12,
|
||||||
|
(Color){150, 150, 150, 150});
|
||||||
|
|
||||||
|
/* Weapon panel */
|
||||||
|
const char* weaponName = (weapon == SUIC_WEAPON_PISTOL) ? "PISTOL" : "RIFLE";
|
||||||
|
Color weaponColor = (weapon == SUIC_WEAPON_PISTOL)
|
||||||
|
? (Color){100, 200, 255, 255}
|
||||||
|
: (Color){255, 150, 100, 255};
|
||||||
|
|
||||||
|
DrawRectangle(sw - 260, sh - 120, 250, 110, (Color){0, 0, 0, 180});
|
||||||
|
DrawText(weaponName, sw - 250, sh - 110, 28, weaponColor);
|
||||||
|
DrawText(TextFormat("%d", mag), sw - 250, sh - 75, 40, RAYWHITE);
|
||||||
|
DrawText(TextFormat("/ %d", reserve), sw - 140, sh - 65, 24, (Color){180, 180, 180, 255});
|
||||||
|
if (reloadTimeLeft > 0)
|
||||||
|
DrawText("RELOADING...", sw - 250, sh - 30, 20, (Color){255, 200, 80, 255});
|
||||||
|
else if (mag == 0)
|
||||||
|
DrawText("RELOAD!", sw - 250, sh - 30, 20, (Color){255, 80, 80, 255});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== CROSSHAIR ===== */
|
||||||
|
|
||||||
|
void suic_ui_draw_crosshair(int screenW, int screenH, float spread, bool scoped) {
|
||||||
|
int cx = screenW / 2, cy = screenH / 2;
|
||||||
|
int gap = scoped ? 2 : 6 + (int)spread;
|
||||||
|
int len = scoped ? 5 : 10;
|
||||||
|
int thick = 2;
|
||||||
|
Color col = (Color){240, 240, 245, 220};
|
||||||
|
|
||||||
|
DrawRectangle(cx - gap - len, cy - thick / 2, len, thick, col);
|
||||||
|
DrawRectangle(cx + gap, cy - thick / 2, len, thick, col);
|
||||||
|
DrawRectangle(cx - thick / 2, cy - gap - len, thick, len, col);
|
||||||
|
DrawRectangle(cx - thick / 2, cy + gap, thick, len, col);
|
||||||
|
DrawCircleLines(cx, cy, scoped ? 1.5f : 3.0f, (Color){240, 240, 245, 160});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== ROOM STATE ===== */
|
||||||
|
|
||||||
|
void suic_ui_draw_room_state(int screenW, int screenH, int state,
|
||||||
|
float countdown, const char* winnerName) {
|
||||||
|
if (state == 0) { /* Waiting */
|
||||||
|
DrawRectangle(screenW / 2 - 150, screenH / 2 - 50, 350, 100, (Color){0, 0, 0, 200});
|
||||||
|
DrawText("WAITING FOR PLAYERS", screenW / 2 - 120, screenH / 2 - 30, 24, RAYWHITE);
|
||||||
|
DrawText("Need at least 2 players", screenW / 2 - 100, screenH / 2 - 5, 18,
|
||||||
|
(Color){200, 200, 200, 255});
|
||||||
|
} else if (state == 1) { /* Counting down */
|
||||||
|
DrawRectangle(screenW / 2 - 150, screenH / 2 - 50, 350, 100, (Color){0, 0, 0, 200});
|
||||||
|
DrawText("GAME STARTING SOON", screenW / 2 - 120, screenH / 2 - 30, 24,
|
||||||
|
(Color){255, 255, 80, 255});
|
||||||
|
DrawText(TextFormat("%.1f seconds", countdown), screenW / 2 - 60,
|
||||||
|
screenH / 2 - 5, 20, RAYWHITE);
|
||||||
|
} else if (state == 3) { /* Finished */
|
||||||
|
DrawRectangle(screenW / 2 - 200, screenH / 2 - 50, 450, 100, (Color){0, 0, 0, 200});
|
||||||
|
DrawText("GAME FINISHED", screenW / 2 - 80, screenH / 2 - 30, 28,
|
||||||
|
(Color){255, 80, 80, 255});
|
||||||
|
if (winnerName && winnerName[0]) {
|
||||||
|
DrawText(TextFormat("Winner: %s", winnerName), screenW / 2 - 100, screenH / 2 - 5,
|
||||||
|
24, (Color){255, 255, 80, 255});
|
||||||
|
} else {
|
||||||
|
DrawText("No winner", screenW / 2 - 50, screenH / 2 - 5, 24, RAYWHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== NAMEPLATES ===== */
|
||||||
|
|
||||||
|
void suic_ui_draw_nameplate(int screenW, int screenH, float worldX, float worldY, float worldZ,
|
||||||
|
const char* username, bool isLocalPlayer,
|
||||||
|
float camPosX, float camPosY, float camPosZ,
|
||||||
|
float camTargetX, float camTargetY, float camTargetZ) {
|
||||||
|
if (!username || !username[0]) return;
|
||||||
|
|
||||||
|
Vector3 head = {worldX, worldY + 1.2f, worldZ};
|
||||||
|
Vector3 camPos = {camPosX, camPosY, camPosZ};
|
||||||
|
Vector3 camTarget = {camTargetX, camTargetY, camTargetZ};
|
||||||
|
|
||||||
|
/* Check if in front of camera */
|
||||||
|
Vector3 camForward = {camTarget.x - camPos.x, camTarget.y - camPos.y, camTarget.z - camPos.z};
|
||||||
|
float len = sqrtf(camForward.x * camForward.x + camForward.y * camForward.y + camForward.z * camForward.z);
|
||||||
|
if (len > 0) {
|
||||||
|
camForward.x /= len;
|
||||||
|
camForward.y /= len;
|
||||||
|
camForward.z /= len;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 toHead = {head.x - camPos.x, head.y - camPos.y, head.z - camPos.z};
|
||||||
|
float dot = camForward.x * toHead.x + camForward.y * toHead.y + camForward.z * toHead.z;
|
||||||
|
if (dot <= 0.0f) return;
|
||||||
|
|
||||||
|
Camera3D cam = {0};
|
||||||
|
cam.position = camPos;
|
||||||
|
cam.target = camTarget;
|
||||||
|
cam.up = (Vector3){0, 1, 0};
|
||||||
|
cam.fovy = 75.0f;
|
||||||
|
cam.projection = CAMERA_PERSPECTIVE;
|
||||||
|
|
||||||
|
Vector2 s = GetWorldToScreen(head, cam);
|
||||||
|
if (s.x < -200 || s.x > screenW + 200 || s.y < -200 || s.y > screenH + 200)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int fontSize = 18;
|
||||||
|
int w = MeasureText(username, fontSize);
|
||||||
|
Color tc = isLocalPlayer ? (Color){180, 230, 255, 255} : RAYWHITE;
|
||||||
|
DrawText(username, (int)(s.x - w / 2), (int)(s.y - fontSize), fontSize, tc);
|
||||||
|
}
|
||||||
28
game/suic_ui.h
Normal file
28
game/suic_ui.h
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI rendering API for game client.
|
||||||
|
* HUD, crosshair, room state overlays, username prompt.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Username prompt (blocking) */
|
||||||
|
void suic_ui_username_prompt(char* outName, int maxLen);
|
||||||
|
|
||||||
|
/* HUD */
|
||||||
|
void suic_ui_draw_hud(int hp, int weapon, int mag, int reserve,
|
||||||
|
int medkits, int reloadTimeLeft);
|
||||||
|
|
||||||
|
/* Crosshair */
|
||||||
|
void suic_ui_draw_crosshair(int screenW, int screenH, float spread, bool scoped);
|
||||||
|
|
||||||
|
/* Room state overlay */
|
||||||
|
void suic_ui_draw_room_state(int screenW, int screenH, int state,
|
||||||
|
float countdown, const char* winnerName);
|
||||||
|
|
||||||
|
/* Nameplates */
|
||||||
|
void suic_ui_draw_nameplate(int screenW, int screenH, float worldX, float worldY, float worldZ,
|
||||||
|
const char* username, bool isLocalPlayer,
|
||||||
|
float camPosX, float camPosY, float camPosZ,
|
||||||
|
float camTargetX, float camTargetY, float camTargetZ);
|
||||||
BIN
game/suic_ui.o
Normal file
BIN
game/suic_ui.o
Normal file
Binary file not shown.
|
|
@ -4,9 +4,6 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.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
|
// Helper for allocating arrays
|
||||||
static void* suic_alloc_array(const TypeInfo* type, size_t elem_size, size_t len, void* init_data) {
|
static void* suic_alloc_array(const TypeInfo* type, size_t elem_size, size_t len, void* init_data) {
|
||||||
|
|
@ -22,12 +19,12 @@ static void* suic_alloc_struct(const TypeInfo* type, size_t size, void* init_dat
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void* gc_alloc(const TypeInfo* type, size_t size);
|
||||||
|
void gc_init(void);
|
||||||
|
void gc_shutdown(void);
|
||||||
|
|
||||||
int suic_main(void);
|
int suic_main(void);
|
||||||
|
|
||||||
|
|
||||||
int suic_main(void) {
|
int suic_main(void) {
|
||||||
char* SERVER_HOST = suic_alloc_array(NULL, sizeof(char), 10, "127.0.0.1");
|
char* SERVER_HOST = suic_alloc_array(NULL, sizeof(char), 10, "127.0.0.1");
|
||||||
int SERVER_PORT = 8888;
|
int SERVER_PORT = 8888;
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ int suic_main(void);
|
||||||
|
|
||||||
int suic_main(void) {
|
int suic_main(void) {
|
||||||
int* arr = suic_alloc_array(NULL, sizeof(int), 4, (int[]){1, 2, 3, 4});
|
int* arr = suic_alloc_array(NULL, sizeof(int), 4, (int[]){1, 2, 3, 4});
|
||||||
int x = arr[0];
|
int x = arr[2];
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
BIN
tests/arrays.o
BIN
tests/arrays.o
Binary file not shown.
|
|
@ -2,6 +2,6 @@
|
||||||
fn main() -> int do
|
fn main() -> int do
|
||||||
let arr = [1, 2, 3, 4];
|
let arr = [1, 2, 3, 4];
|
||||||
# let empty = [];
|
# let empty = [];
|
||||||
let x = arr[0];
|
let x = arr[1];
|
||||||
x
|
x
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue