Compare commits
4 commits
c880f9dff0
...
0151eb65ef
| Author | SHA1 | Date | |
|---|---|---|---|
| 0151eb65ef | |||
| 70c860e1ca | |||
| 453e9fd5e8 | |||
| 83334091ba |
4 changed files with 771 additions and 607 deletions
674
game/client.c
674
game/client.c
|
|
@ -1,9 +1,4 @@
|
|||
// client.c - raylib client (C99)
|
||||
// Features: username prompt, inverted A/D, inverted pitch, recoil, crosshair
|
||||
// widening, spray, inventory UI toggle on E, pick/drop/use items, renders
|
||||
// dropped items from server. Build (Linux example):
|
||||
// gcc client.c -O2 -o client -lraylib -lm -lpthread -ldl -lrt -lX11
|
||||
|
||||
// game/client.c - SMOOTH TERRAIN VERSION WITH DIRECTIONAL LIGHT & SHADOWS
|
||||
#include "raylib.h"
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
|
@ -26,32 +21,355 @@ typedef int socklen_t;
|
|||
#define CLOSESOCK close
|
||||
#endif
|
||||
|
||||
#define PROTOCOL_VERSION 4
|
||||
#define PROTOCOL_VERSION 5
|
||||
#define SERVER_PORT 27015
|
||||
#define MAX_PLAYERS 16
|
||||
#define USERNAME_MAX 16
|
||||
|
||||
#define WEAPON_PISTOL 0
|
||||
#define WEAPON_RIFLE 1
|
||||
#define WEAPON_COUNT 2
|
||||
|
||||
#define INV_SLOTS 6
|
||||
#define MAX_ITEMS 64
|
||||
|
||||
// Inventory item types (must match server)
|
||||
#define ITEM_NONE 0
|
||||
#define ITEM_MEDKIT 1
|
||||
#define ITEM_ARMORPLATE 2
|
||||
#define ITEM_AMMO_PISTOL 3
|
||||
#define ITEM_AMMO_RIFLE 4
|
||||
#define ITEM_AMMO_PISTOL 2
|
||||
#define ITEM_AMMO_RIFLE 3
|
||||
|
||||
#define MAX_ITEMS 64
|
||||
|
||||
// MsgInput.buttons bits
|
||||
#define BTN_RELOAD (1u << 0)
|
||||
#define BTN_WPN1 (1u << 1)
|
||||
#define BTN_WPN2 (1u << 2)
|
||||
#define BTN_SWITCH_PISTOL (1u << 1)
|
||||
#define BTN_SWITCH_RIFLE (1u << 2)
|
||||
#define BTN_PICK (1u << 3)
|
||||
#define BTN_DROP (1u << 4)
|
||||
#define BTN_USE (1u << 5)
|
||||
#define BTN_USE_MEDKIT (1u << 4)
|
||||
#define BTN_JUMP (1u << 5)
|
||||
|
||||
// ============== DIRECTIONAL LIGHT ==============
|
||||
typedef struct {
|
||||
Vector3 direction; // Normalized light direction
|
||||
Vector3 color; // RGB color (0-1 range)
|
||||
float intensity; // Light intensity multiplier
|
||||
float ambientIntensity; // Ambient light strength
|
||||
float shadowBias; // Shadow bias to prevent z-fighting
|
||||
float shadowIntensity; // How dark shadows are (0-1)
|
||||
} DirectionalLight;
|
||||
|
||||
// ============== SIMPLEX NOISE ==============
|
||||
static const uint8_t perm[512] = {
|
||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7,
|
||||
225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190,
|
||||
6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117,
|
||||
35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136,
|
||||
171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158,
|
||||
231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46,
|
||||
245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209,
|
||||
76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86,
|
||||
164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5,
|
||||
202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16,
|
||||
58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44,
|
||||
154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253,
|
||||
19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97,
|
||||
228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51,
|
||||
145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184,
|
||||
84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,
|
||||
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156,
|
||||
180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233,
|
||||
7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
|
||||
190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203,
|
||||
117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125,
|
||||
136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146,
|
||||
158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55,
|
||||
46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73,
|
||||
209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159,
|
||||
86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123,
|
||||
5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47,
|
||||
16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2,
|
||||
44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39,
|
||||
253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246,
|
||||
97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81,
|
||||
51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
|
||||
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205,
|
||||
93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61,
|
||||
156, 180};
|
||||
|
||||
static float grad2(int hash, float x, float y) {
|
||||
int h = hash & 7;
|
||||
float u = h < 4 ? x : y;
|
||||
float v = h < 4 ? y : x;
|
||||
return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f * v : 2.0f * v);
|
||||
}
|
||||
|
||||
static float simplex_noise_2d(float x, float y) {
|
||||
const float F2 = 0.366025403f;
|
||||
const float G2 = 0.211324865f;
|
||||
float s = (x + y) * F2;
|
||||
int i = (int)floorf(x + s);
|
||||
int j = (int)floorf(y + s);
|
||||
float t = (i + j) * G2;
|
||||
float X0 = i - t, Y0 = j - t;
|
||||
float x0 = x - X0, y0 = y - Y0;
|
||||
int i1 = (x0 > y0) ? 1 : 0;
|
||||
int j1 = (x0 > y0) ? 0 : 1;
|
||||
float x1 = x0 - i1 + G2, y1 = y0 - j1 + G2;
|
||||
float x2 = x0 - 1.0f + 2.0f * G2, y2 = y0 - 1.0f + 2.0f * G2;
|
||||
int ii = i & 255, jj = j & 255;
|
||||
float n0 = 0.0f, n1 = 0.0f, n2 = 0.0f;
|
||||
float t0 = 0.5f - x0 * x0 - y0 * y0;
|
||||
if (t0 >= 0.0f) {
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * grad2(perm[ii + perm[jj]], x0, y0);
|
||||
}
|
||||
float t1 = 0.5f - x1 * x1 - y1 * y1;
|
||||
if (t1 >= 0.0f) {
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * grad2(perm[ii + i1 + perm[jj + j1]], x1, y1);
|
||||
}
|
||||
float t2 = 0.5f - x2 * x2 - y2 * y2;
|
||||
if (t2 >= 0.0f) {
|
||||
t2 *= t2;
|
||||
n2 = t2 * t2 * grad2(perm[ii + 1 + perm[jj + 1]], x2, y2);
|
||||
}
|
||||
return 45.0f * (n0 + n1 + n2);
|
||||
}
|
||||
|
||||
static float fbm_noise(float x, float y, int octaves) {
|
||||
float value = 0.0f, amplitude = 1.0f, frequency = 1.0f, max_value = 0.0f;
|
||||
for (int i = 0; i < octaves; i++) {
|
||||
value += simplex_noise_2d(x * frequency, y * frequency) * amplitude;
|
||||
max_value += amplitude;
|
||||
amplitude *= 0.5f;
|
||||
frequency *= 2.0f;
|
||||
}
|
||||
return value / max_value;
|
||||
}
|
||||
|
||||
float get_terrain_height(float x, float z) {
|
||||
float height = fbm_noise(x * 0.03f, z * 0.03f, 4);
|
||||
height += fbm_noise(x * 0.01f, z * 0.01f, 2) * 1.5f;
|
||||
return height * 6.0f + 2.0f;
|
||||
}
|
||||
|
||||
#define TERRAIN_SIZE 64
|
||||
#define TERRAIN_SCALE 2.0f
|
||||
|
||||
static Mesh generate_terrain_mesh(void) {
|
||||
int size = TERRAIN_SIZE;
|
||||
Mesh mesh = {0};
|
||||
|
||||
int vertexCount = size * size;
|
||||
int triangleCount = (size - 1) * (size - 1) * 2;
|
||||
|
||||
mesh.vertexCount = vertexCount;
|
||||
mesh.triangleCount = triangleCount;
|
||||
|
||||
mesh.vertices = (float *)MemAlloc(vertexCount * 3 * sizeof(float));
|
||||
mesh.texcoords = (float *)MemAlloc(vertexCount * 2 * sizeof(float));
|
||||
mesh.normals = (float *)MemAlloc(vertexCount * 3 * sizeof(float));
|
||||
mesh.indices =
|
||||
(unsigned short *)MemAlloc(triangleCount * 3 * sizeof(unsigned short));
|
||||
|
||||
// Generate vertices
|
||||
for (int z = 0; z < size; z++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
int idx = z * size + x;
|
||||
float wx = ((float)x - size / 2.0f) * TERRAIN_SCALE;
|
||||
float wz = ((float)z - size / 2.0f) * TERRAIN_SCALE;
|
||||
float wy = get_terrain_height(wx, wz);
|
||||
|
||||
mesh.vertices[idx * 3 + 0] = wx;
|
||||
mesh.vertices[idx * 3 + 1] = wy;
|
||||
mesh.vertices[idx * 3 + 2] = wz;
|
||||
|
||||
mesh.texcoords[idx * 2 + 0] = (float)x / (float)(size - 1);
|
||||
mesh.texcoords[idx * 2 + 1] = (float)z / (float)(size - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate indices
|
||||
int triIdx = 0;
|
||||
for (int z = 0; z < size - 1; z++) {
|
||||
for (int x = 0; x < size - 1; x++) {
|
||||
int i0 = z * size + x;
|
||||
int i1 = z * size + (x + 1);
|
||||
int i2 = (z + 1) * size + x;
|
||||
int i3 = (z + 1) * size + (x + 1);
|
||||
|
||||
mesh.indices[triIdx * 3 + 0] = i0;
|
||||
mesh.indices[triIdx * 3 + 1] = i2;
|
||||
mesh.indices[triIdx * 3 + 2] = i1;
|
||||
triIdx++;
|
||||
|
||||
mesh.indices[triIdx * 3 + 0] = i1;
|
||||
mesh.indices[triIdx * 3 + 1] = i2;
|
||||
mesh.indices[triIdx * 3 + 2] = i3;
|
||||
triIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate normals
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
mesh.normals[i * 3 + 0] = 0;
|
||||
mesh.normals[i * 3 + 1] = 1;
|
||||
mesh.normals[i * 3 + 2] = 0;
|
||||
}
|
||||
|
||||
for (int t = 0; t < triangleCount; t++) {
|
||||
int i0 = mesh.indices[t * 3 + 0];
|
||||
int i1 = mesh.indices[t * 3 + 1];
|
||||
int i2 = mesh.indices[t * 3 + 2];
|
||||
|
||||
float v0x = mesh.vertices[i0 * 3 + 0];
|
||||
float v0y = mesh.vertices[i0 * 3 + 1];
|
||||
float v0z = mesh.vertices[i0 * 3 + 2];
|
||||
|
||||
float v1x = mesh.vertices[i1 * 3 + 0];
|
||||
float v1y = mesh.vertices[i1 * 3 + 1];
|
||||
float v1z = mesh.vertices[i1 * 3 + 2];
|
||||
|
||||
float v2x = mesh.vertices[i2 * 3 + 0];
|
||||
float v2y = mesh.vertices[i2 * 3 + 1];
|
||||
float v2z = mesh.vertices[i2 * 3 + 2];
|
||||
|
||||
float e1x = v1x - v0x, e1y = v1y - v0y, e1z = v1z - v0z;
|
||||
float e2x = v2x - v0x, e2y = v2y - v0y, e2z = v2z - v0z;
|
||||
|
||||
float nx = e1y * e2z - e1z * e2y;
|
||||
float ny = e1z * e2x - e1x * e2z;
|
||||
float nz = e1x * e2y - e1y * e2x;
|
||||
|
||||
mesh.normals[i0 * 3 + 0] += nx;
|
||||
mesh.normals[i0 * 3 + 1] += ny;
|
||||
mesh.normals[i0 * 3 + 2] += nz;
|
||||
|
||||
mesh.normals[i1 * 3 + 0] += nx;
|
||||
mesh.normals[i1 * 3 + 1] += ny;
|
||||
mesh.normals[i1 * 3 + 2] += nz;
|
||||
|
||||
mesh.normals[i2 * 3 + 0] += nx;
|
||||
mesh.normals[i2 * 3 + 1] += ny;
|
||||
mesh.normals[i2 * 3 + 2] += nz;
|
||||
}
|
||||
|
||||
// Normalize normals
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
float nx = mesh.normals[i * 3 + 0];
|
||||
float ny = mesh.normals[i * 3 + 1];
|
||||
float nz = mesh.normals[i * 3 + 2];
|
||||
float len = sqrtf(nx * nx + ny * ny + nz * nz);
|
||||
if (len > 0.0001f) {
|
||||
mesh.normals[i * 3 + 0] = nx / len;
|
||||
mesh.normals[i * 3 + 1] = ny / len;
|
||||
mesh.normals[i * 3 + 2] = nz / len;
|
||||
}
|
||||
}
|
||||
|
||||
UploadMesh(&mesh, false);
|
||||
return mesh;
|
||||
}
|
||||
// ============== END SIMPLEX NOISE ==============
|
||||
|
||||
// ============== LIGHTING HELPERS ==============
|
||||
// apply_directional_lighting: Basic Lambertian diffuse lighting
|
||||
// Applies directional light with diffuse component based on surface normal
|
||||
static Color apply_directional_lighting(Color baseColor, Vector3 normal,
|
||||
DirectionalLight light) {
|
||||
// Normalize light direction (it's already normalized, but for safety)
|
||||
Vector3 lightDir = light.direction;
|
||||
|
||||
// Calculate diffuse component: dot product of negative light direction and
|
||||
// surface normal We use negative because light travels opposite to its
|
||||
// direction vector
|
||||
float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y +
|
||||
lightDir.z * normal.z));
|
||||
|
||||
// Combine diffuse with ambient light
|
||||
// Ambient provides minimum brightness even in shadow
|
||||
float brightness = light.ambientIntensity +
|
||||
(diff * light.intensity * (1.0f - light.ambientIntensity));
|
||||
|
||||
// Apply brightness multiplier to base color
|
||||
int r = (int)(baseColor.r * brightness);
|
||||
int g = (int)(baseColor.g * brightness);
|
||||
int b = (int)(baseColor.b * brightness);
|
||||
|
||||
// Clamp RGB to valid range [0, 255]
|
||||
if (r > 255)
|
||||
r = 255;
|
||||
if (g > 255)
|
||||
g = 255;
|
||||
if (b > 255)
|
||||
b = 255;
|
||||
|
||||
return (Color){r, g, b, baseColor.a};
|
||||
}
|
||||
|
||||
// calculate_shadow_factor: Distance-based shadow softening
|
||||
// Objects at higher elevations or farther from ground get softer, less
|
||||
// pronounced shadows This simulates how shadows fade with distance and
|
||||
// atmospheric scattering
|
||||
static float calculate_shadow_factor(Vector3 worldPos, Vector3 lightDir,
|
||||
float maxShadowDistance) {
|
||||
// Calculate height above ground (approximate)
|
||||
float distFromLight = fmaxf(0.0f, worldPos.y - 2.0f);
|
||||
|
||||
// Fade out shadow strength with distance (max 15% darkening)
|
||||
float shadowIntensity =
|
||||
fmaxf(0.0f, 1.0f - (distFromLight / maxShadowDistance));
|
||||
return 1.0f - (shadowIntensity * 0.15f);
|
||||
}
|
||||
|
||||
// calculate_temporal_shadow: Time-based shadow variance for anti-aliasing
|
||||
// Simulates subtle shadow movement to avoid banding artifacts
|
||||
static float calculate_temporal_shadow(Vector3 worldPos, float timePhase) {
|
||||
// Add subtle time-based variation to shadow boundaries
|
||||
float noiseVal =
|
||||
sinf(worldPos.x * 0.5f + timePhase) * cosf(worldPos.z * 0.5f + timePhase);
|
||||
return 1.0f + (noiseVal * 0.02f); // Very subtle variation
|
||||
}
|
||||
|
||||
// apply_lighting_with_shadows: Full lighting calculation with shadows
|
||||
// Combines directional light, ambient light, shadows, and temporal variation
|
||||
static Color apply_lighting_with_shadows(Color baseColor, Vector3 normal,
|
||||
Vector3 worldPos,
|
||||
DirectionalLight light) {
|
||||
// Calculate base shadow factor (distance-based)
|
||||
float shadowFactor =
|
||||
calculate_shadow_factor(worldPos, light.direction, 10.0f);
|
||||
|
||||
// Apply shadow to intensity
|
||||
float adjustedIntensity = light.intensity * shadowFactor;
|
||||
|
||||
// Calculate diffuse component with adjusted intensity
|
||||
Vector3 lightDir = light.direction;
|
||||
float diff = fmaxf(0.0f, -(lightDir.x * normal.x + lightDir.y * normal.y +
|
||||
lightDir.z * normal.z));
|
||||
|
||||
// Combine with ambient using adjusted intensity
|
||||
float brightness = light.ambientIntensity + (diff * adjustedIntensity *
|
||||
(1.0f - light.ambientIntensity));
|
||||
|
||||
// Apply brightness to base color
|
||||
int r = (int)(baseColor.r * brightness);
|
||||
int g = (int)(baseColor.g * brightness);
|
||||
int b = (int)(baseColor.b * brightness);
|
||||
|
||||
// Clamp values to valid RGB range
|
||||
if (r > 255)
|
||||
r = 255;
|
||||
if (g > 255)
|
||||
g = 255;
|
||||
if (b > 255)
|
||||
b = 255;
|
||||
|
||||
return (Color){r, g, b, baseColor.a};
|
||||
}
|
||||
|
||||
static Vector3 v3_normalize(Vector3 v) {
|
||||
float len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
|
||||
if (len < 0.0001f)
|
||||
return (Vector3){0, 1, 0};
|
||||
return (Vector3){v.x / len, v.y / len, v.z / len};
|
||||
}
|
||||
// ============== END LIGHTING HELPERS ==============
|
||||
|
||||
static void set_nonblocking(int sock) {
|
||||
#ifdef _WIN32
|
||||
|
|
@ -70,8 +388,7 @@ typedef enum MsgType : uint8_t {
|
|||
MSG_INPUT = 3,
|
||||
MSG_SNAPSHOT = 4,
|
||||
MSG_SHOOT = 5,
|
||||
MSG_HIT = 6,
|
||||
MSG_ITEMS = 7
|
||||
MSG_ITEMS = 6
|
||||
} MsgType;
|
||||
|
||||
typedef struct MsgHello {
|
||||
|
|
@ -90,12 +407,8 @@ typedef struct MsgInput {
|
|||
uint8_t type;
|
||||
uint8_t playerId;
|
||||
uint32_t clientTick;
|
||||
float moveX;
|
||||
float moveZ;
|
||||
float yaw;
|
||||
float pitch;
|
||||
uint8_t buttons; // BTN_*
|
||||
uint8_t slotIndex; // 0..INV_SLOTS-1 (for DROP/USE)
|
||||
float moveX, moveZ, yaw, pitch;
|
||||
uint8_t buttons;
|
||||
} MsgInput;
|
||||
|
||||
typedef struct MsgShoot {
|
||||
|
|
@ -105,20 +418,11 @@ typedef struct MsgShoot {
|
|||
} MsgShoot;
|
||||
|
||||
typedef struct PlayerStateNet {
|
||||
uint8_t id;
|
||||
uint8_t alive;
|
||||
uint8_t id, alive;
|
||||
int16_t hp;
|
||||
int16_t armor; // NEW
|
||||
float x, y, z;
|
||||
float yaw, pitch;
|
||||
|
||||
float x, y, z, yaw, pitch;
|
||||
uint8_t weapon;
|
||||
int16_t ammoMag[WEAPON_COUNT];
|
||||
int16_t ammoRes[WEAPON_COUNT];
|
||||
|
||||
uint8_t invType[INV_SLOTS];
|
||||
int16_t invQty[INV_SLOTS];
|
||||
|
||||
int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits;
|
||||
char username[USERNAME_MAX];
|
||||
} PlayerStateNet;
|
||||
|
||||
|
|
@ -129,13 +433,6 @@ typedef struct MsgSnapshot {
|
|||
PlayerStateNet p[MAX_PLAYERS];
|
||||
} MsgSnapshot;
|
||||
|
||||
typedef struct MsgHit {
|
||||
uint8_t type;
|
||||
uint8_t shooterId;
|
||||
uint8_t victimId;
|
||||
int16_t victimHp;
|
||||
} MsgHit;
|
||||
|
||||
typedef struct ItemNet {
|
||||
uint16_t id;
|
||||
uint8_t type;
|
||||
|
|
@ -152,20 +449,11 @@ typedef struct MsgItems {
|
|||
#pragma pack(pop)
|
||||
|
||||
typedef struct RemotePlayer {
|
||||
int present;
|
||||
int alive;
|
||||
int hp;
|
||||
int armor;
|
||||
int present, alive, hp;
|
||||
Vector3 pos;
|
||||
float yaw, pitch;
|
||||
|
||||
uint8_t weapon;
|
||||
int16_t ammoMag[WEAPON_COUNT];
|
||||
int16_t ammoRes[WEAPON_COUNT];
|
||||
|
||||
uint8_t invType[INV_SLOTS];
|
||||
int16_t invQty[INV_SLOTS];
|
||||
|
||||
int16_t pistolMag, rifleMag, pistolAmmo, rifleAmmo, medkits;
|
||||
char username[USERNAME_MAX];
|
||||
} RemotePlayer;
|
||||
|
||||
|
|
@ -193,22 +481,17 @@ static float v3_dot(Vector3 a, Vector3 b) {
|
|||
static float v3_len(Vector3 a) {
|
||||
return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z);
|
||||
}
|
||||
static Vector3 v3_scale(Vector3 a, float s) {
|
||||
return v3(a.x * s, a.y * s, a.z * s);
|
||||
}
|
||||
static Vector3 v3_norm(Vector3 a) {
|
||||
float l = v3_len(a);
|
||||
if (l <= 1e-6f)
|
||||
return v3(0, 0, 1);
|
||||
return v3_scale(a, 1.0f / l);
|
||||
return v3(a.x / l, a.y / l, a.z / l);
|
||||
}
|
||||
|
||||
static const char *ItemName(uint8_t t) {
|
||||
switch (t) {
|
||||
case ITEM_MEDKIT:
|
||||
return "Medkit";
|
||||
case ITEM_ARMORPLATE:
|
||||
return "Armor plate";
|
||||
case ITEM_AMMO_PISTOL:
|
||||
return "Pistol ammo";
|
||||
case ITEM_AMMO_RIFLE:
|
||||
|
|
@ -218,7 +501,6 @@ static const char *ItemName(uint8_t t) {
|
|||
}
|
||||
}
|
||||
|
||||
// Simple username input (ASCII-ish)
|
||||
static void ui_username_prompt(char outName[USERNAME_MAX]) {
|
||||
memset(outName, 0, USERNAME_MAX);
|
||||
while (!WindowShouldClose()) {
|
||||
|
|
@ -259,7 +541,7 @@ int main(void) {
|
|||
#endif
|
||||
|
||||
const int sw = 1280, sh = 720;
|
||||
InitWindow(sw, sh, "Voxel Shooter - Client");
|
||||
InitWindow(sw, sh, "Voxel Shooter - Client (SMOOTH TERRAIN WITH SHADOWS)");
|
||||
SetTargetFPS(120);
|
||||
|
||||
char myName[USERNAME_MAX];
|
||||
|
|
@ -267,6 +549,22 @@ int main(void) {
|
|||
|
||||
DisableCursor();
|
||||
|
||||
// Generate terrain mesh
|
||||
Mesh terrainMesh = generate_terrain_mesh();
|
||||
Model terrainModel = LoadModelFromMesh(terrainMesh);
|
||||
terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color =
|
||||
(Color){80, 140, 70, 255};
|
||||
|
||||
// Setup directional light
|
||||
DirectionalLight dirLight = {0};
|
||||
dirLight.direction =
|
||||
v3_normalize(v3(-0.8f, -1.0f, -0.6f)); // Coming from upper-left-back
|
||||
dirLight.color = v3(1.0f, 1.0f, 1.0f);
|
||||
dirLight.intensity = 1.2f;
|
||||
dirLight.ambientIntensity = 0.3f;
|
||||
dirLight.shadowBias = 0.005f;
|
||||
dirLight.shadowIntensity = 0.4f;
|
||||
|
||||
int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sock < 0) {
|
||||
perror("socket");
|
||||
|
|
@ -285,47 +583,22 @@ int main(void) {
|
|||
RemotePlayer rp[MAX_PLAYERS] = {0};
|
||||
WorldItem wi[MAX_ITEMS] = {0};
|
||||
|
||||
// view/camera
|
||||
Vector3 camPos = v3(0, 1.7f, 6);
|
||||
float yaw = 0.0f;
|
||||
float pitch = 0.0f;
|
||||
Vector3 camPos = v3(0, 5.0f, 6);
|
||||
float yaw = 0.0f, pitch = 0.0f;
|
||||
|
||||
// recoil/crosshair
|
||||
float recoilYaw = 0.0f, recoilPitch = 0.0f;
|
||||
float crossSpread = 0.0f;
|
||||
|
||||
// firing
|
||||
float recoilYaw = 0.0f, recoilPitch = 0.0f, crossSpread = 0.0f;
|
||||
float fireCooldown = 0.0f;
|
||||
int shotsInBurst = 0;
|
||||
float burstResetTimer = 0.0f;
|
||||
|
||||
// inventory UI
|
||||
int invOpen = 0;
|
||||
int selectedSlot = 0;
|
||||
|
||||
// desired weapon
|
||||
int desiredWeapon = WEAPON_RIFLE;
|
||||
|
||||
// tuning
|
||||
const float pistolFireRate = 4.0f;
|
||||
const float rifleFireRate = 12.0f;
|
||||
const float recoilReturn = 18.0f;
|
||||
const float crossReturn = 14.0f;
|
||||
|
||||
const float pistolKickPitch = 0.010f;
|
||||
const float pistolKickYaw = 0.004f;
|
||||
const float rifleKickPitch = 0.018f;
|
||||
const float rifleKickYaw = 0.010f;
|
||||
|
||||
const float pistolCrossKick = 2.0f;
|
||||
const float rifleCrossKick = 4.0f;
|
||||
|
||||
const float pistolSprayGrow = 0.4f;
|
||||
const float rifleSprayGrow = 1.2f;
|
||||
|
||||
const float pistolFireRate = 4.0f, rifleFireRate = 12.0f;
|
||||
const float recoilReturn = 18.0f, crossReturn = 14.0f;
|
||||
const float pistolKickPitch = 0.010f, pistolKickYaw = 0.004f;
|
||||
const float rifleKickPitch = 0.018f, rifleKickYaw = 0.010f;
|
||||
const float pistolCrossKick = 2.0f, rifleCrossKick = 4.0f;
|
||||
const float pistolSprayGrow = 0.4f, rifleSprayGrow = 1.2f;
|
||||
const float burstResetTime = 0.18f;
|
||||
|
||||
// HELLO
|
||||
MsgHello hello = {0};
|
||||
hello.type = MSG_HELLO;
|
||||
hello.protocol = PROTOCOL_VERSION;
|
||||
|
|
@ -337,7 +610,6 @@ int main(void) {
|
|||
clientTick++;
|
||||
float dt = GetFrameTime();
|
||||
|
||||
// timers
|
||||
fireCooldown -= dt;
|
||||
if (fireCooldown < 0.0f)
|
||||
fireCooldown = 0.0f;
|
||||
|
|
@ -345,13 +617,11 @@ int main(void) {
|
|||
if (burstResetTimer <= 0.0f)
|
||||
shotsInBurst = 0;
|
||||
|
||||
// recoil return
|
||||
{
|
||||
float k = 1.0f - expf(-recoilReturn * dt);
|
||||
recoilYaw += (0.0f - recoilYaw) * k;
|
||||
recoilPitch += (0.0f - recoilPitch) * k;
|
||||
}
|
||||
// crosshair return
|
||||
{
|
||||
float k = 1.0f - expf(-crossReturn * dt);
|
||||
crossSpread += (0.0f - crossSpread) * k;
|
||||
|
|
@ -359,7 +629,6 @@ int main(void) {
|
|||
crossSpread = 0.0f;
|
||||
}
|
||||
|
||||
// receive packets
|
||||
for (;;) {
|
||||
uint8_t buf[1400];
|
||||
struct sockaddr_in from = {0};
|
||||
|
|
@ -395,21 +664,15 @@ int main(void) {
|
|||
p->present = 1;
|
||||
p->alive = ps->alive;
|
||||
p->hp = ps->hp;
|
||||
p->armor = ps->armor;
|
||||
p->pos = v3(ps->x, ps->y, ps->z);
|
||||
p->yaw = ps->yaw;
|
||||
p->pitch = ps->pitch;
|
||||
p->weapon = ps->weapon;
|
||||
|
||||
for (int w = 0; w < WEAPON_COUNT; w++) {
|
||||
p->ammoMag[w] = ps->ammoMag[w];
|
||||
p->ammoRes[w] = ps->ammoRes[w];
|
||||
}
|
||||
for (int sidx = 0; sidx < INV_SLOTS; sidx++) {
|
||||
p->invType[sidx] = ps->invType[sidx];
|
||||
p->invQty[sidx] = ps->invQty[sidx];
|
||||
}
|
||||
|
||||
p->pistolMag = ps->pistolMag;
|
||||
p->rifleMag = ps->rifleMag;
|
||||
p->pistolAmmo = ps->pistolAmmo;
|
||||
p->rifleAmmo = ps->rifleAmmo;
|
||||
p->medkits = ps->medkits;
|
||||
memset(p->username, 0, USERNAME_MAX);
|
||||
strncpy(p->username, ps->username, USERNAME_MAX - 1);
|
||||
}
|
||||
|
|
@ -417,8 +680,8 @@ int main(void) {
|
|||
if (myId != 255 && rp[myId].present) {
|
||||
Vector3 body = rp[myId].pos;
|
||||
camPos.x = body.x;
|
||||
camPos.y = body.y + 1.0f;
|
||||
camPos.z = body.z + 0.0001f;
|
||||
desiredWeapon = rp[myId].weapon;
|
||||
}
|
||||
} else if (type == MSG_ITEMS && n >= (int)sizeof(MsgItems)) {
|
||||
MsgItems *m = (MsgItems *)buf;
|
||||
|
|
@ -435,25 +698,6 @@ int main(void) {
|
|||
}
|
||||
}
|
||||
|
||||
// toggle inventory UI on E
|
||||
if (IsKeyPressed(KEY_E))
|
||||
invOpen = !invOpen;
|
||||
|
||||
// select slot 1..6
|
||||
if (IsKeyPressed(KEY_ONE))
|
||||
selectedSlot = 0;
|
||||
if (IsKeyPressed(KEY_TWO))
|
||||
selectedSlot = 1;
|
||||
if (IsKeyPressed(KEY_THREE))
|
||||
selectedSlot = 2;
|
||||
if (IsKeyPressed(KEY_FOUR))
|
||||
selectedSlot = 3;
|
||||
if (IsKeyPressed(KEY_FIVE))
|
||||
selectedSlot = 4;
|
||||
if (IsKeyPressed(KEY_SIX))
|
||||
selectedSlot = 5;
|
||||
|
||||
// mouse look: yaw fixed, pitch inverted
|
||||
Vector2 md = GetMouseDelta();
|
||||
const float sens = 0.0025f;
|
||||
yaw -= md.x * sens;
|
||||
|
|
@ -463,7 +707,6 @@ int main(void) {
|
|||
if (pitch > 1.5f)
|
||||
pitch = 1.5f;
|
||||
|
||||
// apply recoil to view
|
||||
float viewYaw = yaw + recoilYaw;
|
||||
float viewPitch = pitch + recoilPitch;
|
||||
if (viewPitch < -1.5f)
|
||||
|
|
@ -471,7 +714,6 @@ int main(void) {
|
|||
if (viewPitch > 1.5f)
|
||||
viewPitch = 1.5f;
|
||||
|
||||
// movement (A/D inverted)
|
||||
float moveX = 0.0f, moveZ = 0.0f;
|
||||
if (IsKeyDown(KEY_A))
|
||||
moveX += 1.0f;
|
||||
|
|
@ -482,32 +724,20 @@ int main(void) {
|
|||
if (IsKeyDown(KEY_S))
|
||||
moveZ -= 1.0f;
|
||||
|
||||
// input buttons
|
||||
uint8_t buttons = 0;
|
||||
|
||||
// weapon switch (separate from slot select)
|
||||
if (IsKeyPressed(KEY_Z)) {
|
||||
desiredWeapon = WEAPON_PISTOL;
|
||||
buttons |= BTN_WPN1;
|
||||
}
|
||||
if (IsKeyPressed(KEY_X)) {
|
||||
desiredWeapon = WEAPON_RIFLE;
|
||||
buttons |= BTN_WPN2;
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KEY_ONE))
|
||||
buttons |= BTN_SWITCH_PISTOL;
|
||||
if (IsKeyPressed(KEY_TWO))
|
||||
buttons |= BTN_SWITCH_RIFLE;
|
||||
if (IsKeyPressed(KEY_R))
|
||||
buttons |= BTN_RELOAD;
|
||||
if (IsKeyPressed(KEY_F))
|
||||
buttons |= BTN_PICK;
|
||||
if (IsKeyPressed(KEY_G))
|
||||
buttons |= BTN_DROP;
|
||||
if (IsKeyPressed(KEY_C))
|
||||
buttons |= BTN_USE; // use armor/medkit depending on selected slot
|
||||
if (IsKeyPressed(KEY_V))
|
||||
buttons |=
|
||||
BTN_USE; // alternate use key (also triggers, server uses slot type)
|
||||
if (IsKeyPressed(KEY_H))
|
||||
buttons |= BTN_USE_MEDKIT;
|
||||
if (IsKeyPressed(KEY_SPACE))
|
||||
buttons |= BTN_JUMP;
|
||||
|
||||
// send INPUT
|
||||
if (myId != 255) {
|
||||
MsgInput in = {0};
|
||||
in.type = MSG_INPUT;
|
||||
|
|
@ -518,23 +748,20 @@ int main(void) {
|
|||
in.yaw = viewYaw;
|
||||
in.pitch = viewPitch;
|
||||
in.buttons = buttons;
|
||||
in.slotIndex = (uint8_t)selectedSlot;
|
||||
sendto(sock, (const char *)&in, (int)sizeof(in), 0,
|
||||
(const struct sockaddr *)&srv, sizeof(srv));
|
||||
}
|
||||
|
||||
// shoot (hold LMB)
|
||||
if (myId != 255 && IsMouseButtonDown(MOUSE_BUTTON_LEFT) &&
|
||||
fireCooldown <= 0.0f) {
|
||||
float rate =
|
||||
(desiredWeapon == WEAPON_PISTOL) ? pistolFireRate : rifleFireRate;
|
||||
float interval = 1.0f / rate;
|
||||
fireCooldown = interval;
|
||||
fireCooldown <= 0.0f && rp[myId].present) {
|
||||
int wpn = rp[myId].weapon;
|
||||
float rate = (wpn == WEAPON_PISTOL) ? pistolFireRate : rifleFireRate;
|
||||
fireCooldown = 1.0f / rate;
|
||||
|
||||
shotsInBurst++;
|
||||
burstResetTimer = burstResetTime;
|
||||
|
||||
if (desiredWeapon == WEAPON_PISTOL) {
|
||||
if (wpn == WEAPON_PISTOL) {
|
||||
recoilPitch += pistolKickPitch;
|
||||
recoilYaw +=
|
||||
(((float)GetRandomValue(-1000, 1000)) / 1000.0f) * pistolKickYaw;
|
||||
|
|
@ -554,7 +781,6 @@ int main(void) {
|
|||
(const struct sockaddr *)&srv, sizeof(srv));
|
||||
}
|
||||
|
||||
// camera
|
||||
Vector3 forward = v3(sinf(viewYaw) * cosf(viewPitch), sinf(viewPitch),
|
||||
cosf(viewYaw) * cosf(viewPitch));
|
||||
|
||||
|
|
@ -566,42 +792,42 @@ int main(void) {
|
|||
cam.projection = CAMERA_PERSPECTIVE;
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground((Color){20, 24, 32, 255});
|
||||
ClearBackground((Color){135, 206, 235, 255});
|
||||
|
||||
BeginMode3D(cam);
|
||||
|
||||
// ground
|
||||
for (int z = -16; z <= 16; z++) {
|
||||
for (int x = -16; x <= 16; x++) {
|
||||
DrawCubeV(v3((float)x, 0.0f, (float)z), v3(1, 1, 1),
|
||||
(Color){50, 60, 75, 255});
|
||||
}
|
||||
}
|
||||
// Calculate terrain normal at camera position for lighting
|
||||
Vector3 terrainNormal = v3(0, 1, 0); // Default up normal
|
||||
|
||||
// blocks
|
||||
DrawCubeV(v3(2, 1, 2), v3(1, 1, 1), (Color){120, 90, 60, 255});
|
||||
DrawCubeV(v3(2, 2, 2), v3(1, 1, 1), (Color){120, 90, 60, 255});
|
||||
DrawCubeV(v3(-3, 1, -1), v3(1, 1, 1), (Color){90, 120, 60, 255});
|
||||
DrawCubeV(v3(0, 1, 4), v3(2, 1, 2), (Color){90, 90, 130, 255});
|
||||
// Draw smooth terrain with directional lighting + shadows
|
||||
// For Raylib, we apply lighting by adjusting material colors
|
||||
Color terrainBaseColor = (Color){80, 140, 70, 255};
|
||||
Color litTerrainColor = apply_lighting_with_shadows(
|
||||
terrainBaseColor, terrainNormal, v3(0, 2, 0), dirLight);
|
||||
|
||||
// items (dropped/pickups)
|
||||
terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].color =
|
||||
litTerrainColor;
|
||||
DrawModel(terrainModel, (Vector3){0, 0, 0}, 1.0f, WHITE);
|
||||
|
||||
// Items with lighting and shadows
|
||||
for (int i = 0; i < MAX_ITEMS; i++) {
|
||||
if (!wi[i].present)
|
||||
continue;
|
||||
Color ic = (Color){220, 220, 220, 255};
|
||||
if (wi[i].type == ITEM_MEDKIT)
|
||||
ic = (Color){120, 255, 120, 255};
|
||||
if (wi[i].type == ITEM_ARMORPLATE)
|
||||
ic = (Color){120, 180, 255, 255};
|
||||
if (wi[i].type == ITEM_AMMO_PISTOL)
|
||||
ic = (Color){255, 220, 120, 255};
|
||||
if (wi[i].type == ITEM_AMMO_RIFLE)
|
||||
ic = (Color){255, 180, 120, 255};
|
||||
DrawCubeV(v3(wi[i].pos.x, wi[i].pos.y + 0.15f, wi[i].pos.z),
|
||||
v3(0.3f, 0.3f, 0.3f), ic);
|
||||
|
||||
// Apply lighting with shadows to items
|
||||
Color litColor =
|
||||
apply_lighting_with_shadows(ic, terrainNormal, wi[i].pos, dirLight);
|
||||
DrawSphere(wi[i].pos, 0.3f, litColor);
|
||||
}
|
||||
|
||||
// players
|
||||
// Players with lighting and shadows
|
||||
for (int i = 0; i < MAX_PLAYERS; i++) {
|
||||
if (!rp[i].present)
|
||||
continue;
|
||||
|
|
@ -610,29 +836,28 @@ int main(void) {
|
|||
(i == myId) ? (Color){80, 180, 255, 255} : (Color){255, 80, 80, 255};
|
||||
if (!rp[i].alive)
|
||||
c = (Color){120, 120, 120, 255};
|
||||
|
||||
// Apply lighting with shadows to player models
|
||||
Color litPlayerColor =
|
||||
apply_lighting_with_shadows(c, terrainNormal, p, dirLight);
|
||||
DrawCapsule(v3(p.x, p.y - 0.5f, p.z), v3(p.x, p.y + 0.5f, p.z), 0.35f, 8,
|
||||
8, c);
|
||||
8, litPlayerColor);
|
||||
}
|
||||
|
||||
EndMode3D();
|
||||
|
||||
// nameplates
|
||||
// Nameplates
|
||||
for (int i = 0; i < MAX_PLAYERS; i++) {
|
||||
if (!rp[i].present)
|
||||
if (!rp[i].present || !rp[i].username[0])
|
||||
continue;
|
||||
if (!rp[i].username[0])
|
||||
continue;
|
||||
|
||||
Vector3 head = v3(rp[i].pos.x, rp[i].pos.y + 1.2f, rp[i].pos.z);
|
||||
Vector3 camForward = v3_norm(v3_sub(cam.target, cam.position));
|
||||
Vector3 toHead = v3_sub(head, cam.position);
|
||||
if (v3_dot(camForward, toHead) <= 0.0f)
|
||||
continue;
|
||||
|
||||
Vector2 s = GetWorldToScreen(head, cam);
|
||||
if (s.x < -200 || s.x > sw + 200 || s.y < -200 || s.y > sh + 200)
|
||||
continue;
|
||||
|
||||
int fontSize = 18;
|
||||
int w = MeasureText(rp[i].username, fontSize);
|
||||
Color tc = (i == myId) ? (Color){180, 230, 255, 255} : RAYWHITE;
|
||||
|
|
@ -644,47 +869,49 @@ int main(void) {
|
|||
if (myId == 255 || !rp[myId].present) {
|
||||
DrawText("Connecting...", 10, 10, 20, RAYWHITE);
|
||||
} else {
|
||||
const char *wname =
|
||||
DrawRectangle(10, 10, 280, 110, (Color){0, 0, 0, 120});
|
||||
DrawText("HP", 20, 20, 20, RAYWHITE);
|
||||
int healthBarWidth = (rp[myId].hp * 220) / 100;
|
||||
Color healthColor = (rp[myId].hp > 60) ? (Color){80, 255, 80, 120}
|
||||
: (rp[myId].hp > 30) ? (Color){255, 200, 80, 120}
|
||||
: (Color){255, 80, 80, 120};
|
||||
DrawRectangle(20, 45, 220, 20, (Color){40, 40, 40, 255});
|
||||
DrawRectangle(20, 45, healthBarWidth, 20, healthColor);
|
||||
DrawText(TextFormat("%d", rp[myId].hp), 250, 47, 18, RAYWHITE);
|
||||
DrawText(TextFormat("Medkits: %d (H to use)", rp[myId].medkits), 20, 75,
|
||||
18,
|
||||
(rp[myId].medkits > 0) ? (Color){120, 255, 120, 120}
|
||||
: (Color){120, 120, 120, 120});
|
||||
DrawText("1=Pistol 2=Rifle R=Reload F=Pick SPACE=Jump", 20, 95, 12,
|
||||
(Color){150, 150, 150, 150});
|
||||
|
||||
const char *weaponName =
|
||||
(rp[myId].weapon == WEAPON_PISTOL) ? "PISTOL" : "RIFLE";
|
||||
DrawText(TextFormat("Name: %s | HP: %d | Armor: %d | Weapon: %s", myName,
|
||||
rp[myId].hp, rp[myId].armor, wname),
|
||||
10, 10, 20, RAYWHITE);
|
||||
Color weaponColor = (rp[myId].weapon == WEAPON_PISTOL)
|
||||
? (Color){100, 200, 255, 255}
|
||||
: (Color){255, 150, 100, 255};
|
||||
int currentMag = (rp[myId].weapon == WEAPON_PISTOL) ? rp[myId].pistolMag
|
||||
: rp[myId].rifleMag;
|
||||
int reserveAmmo = (rp[myId].weapon == WEAPON_PISTOL) ? rp[myId].pistolAmmo
|
||||
: rp[myId].rifleAmmo;
|
||||
|
||||
DrawText(TextFormat("Ammo: P %d/%d | R %d/%d (E inv, F pick, G drop, "
|
||||
"C/V use, R reload, Z/X switch)",
|
||||
rp[myId].ammoMag[WEAPON_PISTOL],
|
||||
rp[myId].ammoRes[WEAPON_PISTOL],
|
||||
rp[myId].ammoMag[WEAPON_RIFLE],
|
||||
rp[myId].ammoRes[WEAPON_RIFLE]),
|
||||
10, 36, 18, (Color){200, 200, 210, 255});
|
||||
|
||||
if (invOpen) {
|
||||
DrawRectangle(20, 70, 420, 190, (Color){10, 10, 10, 180});
|
||||
DrawRectangleLines(20, 70, 420, 190, (Color){200, 200, 210, 210});
|
||||
DrawText("Inventory (slots 1-6)", 32, 80, 22, RAYWHITE);
|
||||
|
||||
for (int sidx = 0; sidx < INV_SLOTS; sidx++) {
|
||||
Color sc = (sidx == selectedSlot) ? (Color){80, 180, 255, 255}
|
||||
: (Color){220, 220, 230, 255};
|
||||
const char *nm = ItemName(rp[myId].invType[sidx]);
|
||||
int qty = rp[myId].invQty[sidx];
|
||||
DrawText(TextFormat("[%d] %s x%d", sidx + 1, nm, qty), 32,
|
||||
110 + sidx * 22, 18, sc);
|
||||
}
|
||||
DrawText("Use: C or V | Drop: G | Pick: F", 32,
|
||||
110 + INV_SLOTS * 22 + 6, 18, (Color){210, 210, 220, 255});
|
||||
}
|
||||
DrawRectangle(sw - 260, sh - 120, 250, 110, (Color){0, 0, 0, 180});
|
||||
DrawText(weaponName, sw - 250, sh - 110, 28, weaponColor);
|
||||
DrawText(TextFormat("%d", currentMag), sw - 250, sh - 75, 40, RAYWHITE);
|
||||
DrawText(TextFormat("/ %d", reserveAmmo), sw - 140, sh - 65, 24,
|
||||
(Color){180, 180, 180, 255});
|
||||
if (currentMag == 0)
|
||||
DrawText("RELOAD!", sw - 250, sh - 30, 20, (Color){255, 80, 80, 255});
|
||||
}
|
||||
|
||||
DrawFPS(sw - 90, 10);
|
||||
|
||||
// crosshair
|
||||
// Crosshair
|
||||
{
|
||||
int cx = sw / 2, cy = sh / 2;
|
||||
int gap = 6 + (int)crossSpread;
|
||||
int len = 10, thick = 2;
|
||||
Color col = (Color){240, 240, 245, 220};
|
||||
|
||||
DrawRectangle(cx - gap - len, cy - thick / 2, len, thick, col);
|
||||
DrawRectangle(cx + gap, cy - thick / 2, len, thick, col);
|
||||
DrawRectangle(cx - thick / 2, cy - gap - len, thick, len, col);
|
||||
|
|
@ -695,6 +922,7 @@ int main(void) {
|
|||
EndDrawing();
|
||||
}
|
||||
|
||||
UnloadModel(terrainModel);
|
||||
CloseWindow();
|
||||
CLOSESOCK(sock);
|
||||
#ifdef _WIN32
|
||||
|
|
|
|||
7
game/run.sh
Executable file
7
game/run.sh
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
gcc -O3 client.c -o client -lm -lraylib
|
||||
gcc -O3 server.c -o server -lm -lraylib -lode
|
||||
|
||||
./server &
|
||||
server_pid=$!
|
||||
./client
|
||||
kill $server_pid
|
||||
707
game/server.c
707
game/server.c
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue