YAYAYYAYAYAYAYAYY IT WORKS AFTER SPLIITTING IT INTO FILES
This commit is contained in:
parent
0d269b83e3
commit
e9e7d938ef
23 changed files with 1568 additions and 0 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/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.
BIN
game/runtime.h.gch
Normal file
BIN
game/runtime.h.gch
Normal file
Binary file not shown.
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.
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.
Loading…
Add table
Add a link
Reference in a new issue