commit 699c6de2cacb555259244389e72d2baa57e69108
parent 97b5b1dab80beed0681d4d9be6986d16f3eb8ac0
Author: averagecoder <averagecoder@noreply.codeberg.org>
Date: Mon, 24 Aug 2026 01:32:18 +0300
feat: add networking
Diffstat:
| M | Makefile | | | 6 | +++++- |
| M | src/build.c | | | 48 | ++++++++++++++++++++++++++++++++++++++++++------ |
| A | src/net.h | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/render.c | | | 488 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ |
| A | src/server.c | | | 282 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
5 files changed, 809 insertions(+), 80 deletions(-)
diff --git a/Makefile b/Makefile
@@ -9,7 +9,8 @@ BIN_DIR = bin
SRC_DIR = src
TARGETS = $(BIN_DIR)/build \
- $(BIN_DIR)/render
+ $(BIN_DIR)/render\
+ $(BIN_DIR)/server
all: $(BIN_DIR) $(TARGETS)
@@ -21,6 +22,9 @@ $(BIN_DIR)/build: $(SRC_DIR)/build.c
$(BIN_DIR)/render: $(SRC_DIR)/render.c
$(CC) $(CFLAGS) $(X11_CFLAGS) -o $@ $(SRC_DIR)/render.c $(LDFLAGS) $(X11_LDFLAGS)
+
+$(BIN_DIR)/server: $(SRC_DIR)/server.c
+ $(CC) $(CFLAGS) -o $@ $(SRC_DIR)/server.c
clean:
rm -rf $(BIN_DIR) assets assets.tar
diff --git a/src/build.c b/src/build.c
@@ -750,7 +750,7 @@ parse_text_ipl(const char *buf, size_t size, FILE *out, struct file_list *fl)
id = atoi(s);
ide = ide_lookup(id);
- if (ide && !is_lod_name(ide->model)) {
+ if (ide && (!is_lod_name(ide->model) || ide->time_on != ide->time_off)) {
char d_name[32], t_name[32];
snprintf(d_name, sizeof(d_name), "%s.dff", ide->model);
@@ -804,7 +804,7 @@ parse_binary_ipl(const uint8_t *buf, size_t size, FILE *out, struct file_list *f
memcpy(&inst, buf + off + i * 40, 40);
ide = ide_lookup(inst.id);
- if (ide && !is_lod_name(ide->model)) {
+ if (ide && (!is_lod_name(ide->model) || ide->time_on != ide->time_off)) {
char d_name[32], t_name[32];
snprintf(d_name, sizeof(d_name), "%s.dff", ide->model);
@@ -888,11 +888,11 @@ convert_txd(const uint8_t *data, size_t size, const char *name, struct arena *a)
uint32_t plt, flt; char n[32], m[32]; uint32_t fmt; char fcc[4];
uint16_t w, h; uint8_t d, lvl, typ, cmp;
} __attribute__((packed)) rh;
- uint32_t mip_size, p_size, *pixels, code, cols[4];
- uint8_t *cmp, r0, g0, b0, r1, g1, b1, a_val, *src;
+ uint32_t mip_size, p_size, *pixels, code, cols[4], pal[256];
+ uint8_t *cmp, r0, g0, b0, r1, g1, b1, a_val, *src, *indices;
uint16_t c0, c1, *src16;
size_t chunk_end;
- int bx, by, is_dxt3, x, y, i, j, k;
+ int bx, by, is_dxt3, x, y, i, j, k, pal_has_alpha;
char t_name[32], out_name[256];
FILE *out;
struct tga_header out_hdr;
@@ -918,6 +918,31 @@ convert_txd(const uint8_t *data, size_t size, const char *name, struct arena *a)
continue;
}
+ if (rh.d == 8) {
+ if (mread(pal, 4, 256, &ms) != 256) {
+ break;
+ }
+ pal_has_alpha = 0;
+ for (k = 0; k < 256; k++) {
+ if ((pal[k] >> 24) != 0) {
+ pal_has_alpha = 1;
+ break;
+ }
+ }
+ if (!pal_has_alpha) {
+ for (k = 0; k < 256; k++) {
+ pal[k] |= (255U << 24);
+ }
+ }
+ } else if (rh.d == 4) {
+ if (mread(pal, 4, 16, &ms) != 16) {
+ break;
+ }
+ for (k = 0; k < 16; k++) {
+ pal[k] |= (255U << 24);
+ }
+ }
+
if (mread(&mip_size, 4, 1, &ms) != 1) {
break;
}
@@ -992,6 +1017,18 @@ convert_txd(const uint8_t *data, size_t size, const char *name, struct arena *a)
}
}
}
+ } else if (rh.d == 8) {
+ indices = malloc(mip_size);
+ if (indices) {
+ mread(indices, 1, mip_size, &ms);
+ for (i = 0; (uint32_t)i < p_size; i++) {
+ uint32_t c = pal[indices[i]];
+ pixels[i] = (c & 0xFF00FF00) |
+ ((c & 0x00FF0000) >> 16) |
+ ((c & 0x000000FF) << 16);
+ }
+ free(indices);
+ }
} else if (rh.d == 32) {
mread(pixels, 1, mip_size, &ms);
if (rh.fmt == 22) {
@@ -1056,7 +1093,6 @@ convert_txd(const uint8_t *data, size_t size, const char *name, struct arena *a)
blur_pixels(pixels, rh.w, rh.h);
}
- /* smart resize to power of two on compiling */
new_w = nearest_pow2(rh.w);
new_h = nearest_pow2(rh.h);
final_pixels = pixels;
diff --git a/src/net.h b/src/net.h
@@ -0,0 +1,65 @@
+#ifndef NET_H
+#define NET_H
+
+#include <stdint.h>
+
+#define NET_PORT 7777
+#define NET_MAGIC 0x415A
+#define MAX_PLAYERS 64
+#define MAX_CHAT_LEN 128
+
+enum packet_type {
+ PKT_JOIN,
+ PKT_JOIN_ACK,
+ PKT_LEAVE,
+ PKT_SYNC,
+ PKT_WORLD_STATE,
+ PKT_CHAT
+};
+
+struct pkt_header {
+ uint16_t magic;
+ uint8_t type;
+ uint8_t player_id;
+} __attribute__((packed));
+
+struct pkt_join {
+ struct pkt_header hdr;
+ char name[24];
+ uint16_t skin;
+} __attribute__((packed));
+
+struct pkt_join_ack {
+ struct pkt_header hdr;
+ uint8_t your_id;
+ float game_time;
+ uint8_t game_weather;
+} __attribute__((packed));
+
+struct pkt_sync {
+ struct pkt_header hdr;
+ float x, y, z;
+ float yaw;
+ uint16_t skin;
+} __attribute__((packed));
+
+struct player_state {
+ uint8_t id;
+ uint16_t skin;
+ float x, y, z;
+ float yaw;
+} __attribute__((packed));
+
+struct pkt_world_state {
+ struct pkt_header hdr;
+ uint8_t player_count;
+ struct player_state players[MAX_PLAYERS];
+} __attribute__((packed));
+
+struct pkt_chat {
+ struct pkt_header hdr;
+ char name[24];
+ char msg[MAX_CHAT_LEN];
+} __attribute__((packed));
+
+#endif
diff --git a/src/render.c b/src/render.c
@@ -1,5 +1,8 @@
+#include <arpa/inet.h>
+#include <netinet/in.h>
#include <sys/ipc.h>
#include <sys/mman.h>
+#include <sys/socket.h>
#include <sys/shm.h>
#include <sys/stat.h>
@@ -23,6 +26,7 @@
#include <unistd.h>
#include "skins.h"
+#include "net.h"
#define WIDTH 640
#define HEIGHT 480
@@ -34,6 +38,7 @@
#define MAP_MAX 3000.0f
#define MAX_RENDER_TRIS 262144
#define TAR_INDEX_SIZE 32768
+#define CHAT_LINES 6
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
@@ -54,6 +59,11 @@ struct col_cell {
uint32_t count;
};
+struct chat_entry {
+ char text[160];
+ float timer;
+};
+
struct timecyc_entry {
uint8_t amb[3], dir[3], sky_top[3], sky_bot[3], water[4];
float far_clp, fog_st;
@@ -76,6 +86,7 @@ struct sys_gfx {
float far_clip_override;
int in_chat, chat_len;
char chat_buf[128];
+ int menu_open;
KeyCode key_w, key_a, key_s, key_d, key_t, key_esc;
KeyCode key_up, key_down, key_left, key_right;
XIC ic;
@@ -114,6 +125,15 @@ struct mesh {
float spr_x, spr_y, spr_z, spr_r, total_area;
};
+struct net_player {
+ uint8_t id;
+ uint16_t skin;
+ float cur_x, cur_y, cur_z;
+ float target_x, target_y, target_z;
+ float cur_yaw, target_yaw;
+ int active;
+};
+
struct transform_array {
float *pos_x, *pos_y, *pos_z;
float *rot_qx, *rot_qy, *rot_qz, *rot_qw;
@@ -139,11 +159,8 @@ struct water_face {
int num;
};
-struct spin_barrier {
- int count;
- int threshold;
- int cycle;
-};
+static pthread_barrier_t barrier_start;
+static pthread_barrier_t barrier_end;
struct tar_header {
char name[100];
@@ -200,7 +217,6 @@ static struct water_face *water_faces = NULL;
static uint32_t water_count = 0;
static struct texture *water_tex = NULL, *sand_tex = NULL;
static pthread_t *workers = NULL;
-static struct spin_barrier barrier_start, barrier_end;
static int num_threads = 1, threads_running = 1;
static struct clip_vertex *v_scratch = NULL;
static size_t v_scratch_cap = 0;
@@ -211,33 +227,15 @@ static struct tar_index_entry tar_idx[TAR_INDEX_SIZE];
static struct asset_cache *g_cache = NULL;
static int active_skin_id = 7;
-static inline void
-spin_barrier_init(struct spin_barrier *b, int count)
-{
- b->count = 0;
- b->threshold = count;
- b->cycle = 0;
-}
+static int net_fd = -1;
+static struct sockaddr_in srv_addr;
+static int my_player_id = -1;
+static int net_connected = 0;
+static struct net_player net_players[MAX_PLAYERS];
-static inline void
-spin_barrier_wait(struct spin_barrier *b)
-{
- int cycle;
-
- cycle = __atomic_load_n(&b->cycle, __ATOMIC_ACQUIRE);
- if (__atomic_add_fetch(&b->count, 1, __ATOMIC_ACQ_REL) == b->threshold) {
- __atomic_store_n(&b->count, 0, __ATOMIC_RELEASE);
- __atomic_store_n(&b->cycle, cycle + 1, __ATOMIC_RELEASE);
- } else {
- while (__atomic_load_n(&b->cycle, __ATOMIC_ACQUIRE) == cycle) {
-#if defined(__i386__) || defined(__x86_64__)
- __builtin_ia32_pause();
-#else
- __asm__ volatile("":::"memory");
-#endif
- }
- }
-}
+static struct chat_entry chat_log[CHAT_LINES];
+static int chat_log_count = 0;
+static char local_nick[24];
static size_t
get_tar_size(const char *octal)
@@ -683,19 +681,235 @@ render_worker(void *arg)
end_y = (id == num_threads - 1) ? HEIGHT - 1 : (id + 1) * HEIGHT / num_threads - 1;
while (1) {
- spin_barrier_wait(&barrier_start);
+ pthread_barrier_wait(&barrier_start);
if (!threads_running) {
break;
}
for (i = 0; i < render_count; i++) {
draw_triangle_band(&render_list[i], start_y, end_y);
}
- spin_barrier_wait(&barrier_end);
+ pthread_barrier_wait(&barrier_end);
}
return (NULL);
}
static void
+chat_add_message(const char *name, const char *msg)
+{
+ int i;
+
+ if (chat_log_count < CHAT_LINES) {
+ snprintf(chat_log[chat_log_count].text,
+ sizeof(chat_log[chat_log_count].text), "[%s]: %s", name, msg);
+ chat_log[chat_log_count].timer = 8.0f;
+ chat_log_count++;
+ } else {
+ for (i = 0; i < CHAT_LINES - 1; i++) {
+ chat_log[i] = chat_log[i + 1];
+ }
+ snprintf(chat_log[CHAT_LINES - 1].text,
+ sizeof(chat_log[CHAT_LINES - 1].text), "[%s]: %s", name, msg);
+ chat_log[CHAT_LINES - 1].timer = 8.0f;
+ }
+}
+
+static void
+net_init(const char *ip, int port, const char *nick)
+{
+ struct pkt_join pj;
+
+ strncpy(local_nick, nick, sizeof(local_nick) - 1);
+ local_nick[sizeof(local_nick) - 1] = '\0';
+
+ net_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (net_fd < 0) {
+ return;
+ }
+ fcntl(net_fd, F_SETFL, O_NONBLOCK);
+
+ memset(&srv_addr, 0, sizeof(srv_addr));
+ srv_addr.sin_family = AF_INET;
+ srv_addr.sin_port = htons(port);
+ inet_pton(AF_INET, ip, &srv_addr.sin_addr);
+
+ memset(&pj, 0, sizeof(pj));
+ pj.hdr.magic = NET_MAGIC;
+ pj.hdr.type = PKT_JOIN;
+ pj.hdr.player_id = 0;
+ strncpy(pj.name, local_nick, sizeof(pj.name) - 1);
+ pj.skin = (uint16_t)active_skin_id;
+
+ sendto(net_fd, &pj, sizeof(pj), 0,
+ (struct sockaddr *)&srv_addr, sizeof(srv_addr));
+}
+
+static void
+net_send_sync(float px, float py, float pz, float pyaw)
+{
+ struct pkt_sync ps;
+
+ if (net_fd < 0 || !net_connected) {
+ return;
+ }
+ memset(&ps, 0, sizeof(ps));
+ ps.hdr.magic = NET_MAGIC;
+ ps.hdr.type = PKT_SYNC;
+ ps.hdr.player_id = (uint8_t)my_player_id;
+ ps.x = px;
+ ps.y = py;
+ ps.z = pz;
+ ps.yaw = pyaw;
+ ps.skin = (uint16_t)active_skin_id;
+
+ sendto(net_fd, &ps, sizeof(ps), 0,
+ (struct sockaddr *)&srv_addr, sizeof(srv_addr));
+}
+
+static void
+net_send_chat(const char *msg)
+{
+ struct pkt_chat pc;
+
+ if (net_fd < 0 || !net_connected) {
+ return;
+ }
+ memset(&pc, 0, sizeof(pc));
+ pc.hdr.magic = NET_MAGIC;
+ pc.hdr.type = PKT_CHAT;
+ pc.hdr.player_id = (uint8_t)my_player_id;
+ strncpy(pc.name, local_nick, sizeof(pc.name) - 1);
+ strncpy(pc.msg, msg, sizeof(pc.msg) - 1);
+
+ sendto(net_fd, &pc, sizeof(pc), 0,
+ (struct sockaddr *)&srv_addr, sizeof(srv_addr));
+}
+
+static void
+net_send_leave(void)
+{
+ struct pkt_header ph;
+
+ if (net_fd < 0 || !net_connected) {
+ return;
+ }
+ ph.magic = NET_MAGIC;
+ ph.type = PKT_LEAVE;
+ ph.player_id = (uint8_t)my_player_id;
+
+ sendto(net_fd, &ph, sizeof(ph), 0,
+ (struct sockaddr *)&srv_addr, sizeof(srv_addr));
+ close(net_fd);
+ net_fd = -1;
+}
+
+static void
+net_poll(struct sys_gfx *g)
+{
+ uint8_t buf[2048];
+ ssize_t n;
+ const struct pkt_header *hdr;
+ const struct pkt_join_ack *ack;
+ const struct pkt_world_state *ws;
+ const struct pkt_chat *pc;
+ int i, id, seen[MAX_PLAYERS];
+
+ if (net_fd < 0) {
+ return;
+ }
+
+ while ((n = recvfrom(net_fd, buf, sizeof(buf), 0, NULL, NULL)) > 0) {
+ if (n < (ssize_t)sizeof(struct pkt_header)) {
+ continue;
+ }
+ hdr = (const struct pkt_header *)buf;
+ if (hdr->magic != NET_MAGIC) {
+ continue;
+ }
+
+ if (hdr->type == PKT_JOIN_ACK) {
+ if (n >= (ssize_t)sizeof(struct pkt_join_ack)) {
+ ack = (const struct pkt_join_ack *)buf;
+ my_player_id = ack->your_id;
+ net_connected = 1;
+ g->game_weather = ack->game_weather;
+ g->game_time_hours = ack->game_time;
+ }
+ } else if (hdr->type == PKT_WORLD_STATE) {
+ if (n >= (ssize_t)(sizeof(struct pkt_header) + 1)) {
+ ws = (const struct pkt_world_state *)buf;
+ memset(seen, 0, sizeof(seen));
+ for (i = 0; i < ws->player_count && i < MAX_PLAYERS; i++) {
+ id = ws->players[i].id;
+ if (id >= MAX_PLAYERS) {
+ continue;
+ }
+ seen[id] = 1;
+ if (!net_players[id].active) {
+ net_players[id].id = id;
+ net_players[id].skin = ws->players[i].skin;
+ net_players[id].cur_x = ws->players[i].x;
+ net_players[id].cur_y = ws->players[i].y;
+ net_players[id].cur_z = ws->players[i].z;
+ net_players[id].cur_yaw = ws->players[i].yaw;
+ net_players[id].target_x = ws->players[i].x;
+ net_players[id].target_y = ws->players[i].y;
+ net_players[id].target_z = ws->players[i].z;
+ net_players[id].target_yaw = ws->players[i].yaw;
+ net_players[id].active = 1;
+ } else {
+ net_players[id].skin = ws->players[i].skin;
+ net_players[id].target_x = ws->players[i].x;
+ net_players[id].target_y = ws->players[i].y;
+ net_players[id].target_z = ws->players[i].z;
+ net_players[id].target_yaw = ws->players[i].yaw;
+ }
+ }
+ for (i = 0; i < MAX_PLAYERS; i++) {
+ if (!seen[i]) {
+ net_players[i].active = 0;
+ }
+ }
+ }
+ } else if (hdr->type == PKT_CHAT) {
+ if (n >= (ssize_t)sizeof(struct pkt_chat)) {
+ pc = (const struct pkt_chat *)buf;
+ chat_add_message(pc->name, pc->msg);
+ }
+ }
+ }
+}
+
+static void
+net_interpolate(float dt)
+{
+ float f, dyaw;
+ int i;
+
+ f = 15.0f * dt;
+ if (f > 1.0f) {
+ f = 1.0f;
+ }
+
+ for (i = 0; i < MAX_PLAYERS; i++) {
+ if (!net_players[i].active || i == my_player_id) {
+ continue;
+ }
+ net_players[i].cur_x += (net_players[i].target_x - net_players[i].cur_x) * f;
+ net_players[i].cur_y += (net_players[i].target_y - net_players[i].cur_y) * f;
+ net_players[i].cur_z += (net_players[i].target_z - net_players[i].cur_z) * f;
+
+ dyaw = net_players[i].target_yaw - net_players[i].cur_yaw;
+ while (dyaw > 3.14159265f) {
+ dyaw -= 6.2831853f;
+ }
+ while (dyaw < -3.14159265f) {
+ dyaw += 6.2831853f;
+ }
+ net_players[i].cur_yaw += dyaw * f;
+ }
+}
+
+static void
gfx_init(struct sys_gfx *g)
{
XIM im;
@@ -781,8 +995,8 @@ gfx_init(struct sys_gfx *g)
if (num_threads < 1) {
num_threads = 4;
}
- spin_barrier_init(&barrier_start, num_threads + 1);
- spin_barrier_init(&barrier_end, num_threads + 1);
+ pthread_barrier_init(&barrier_start, NULL, num_threads + 1);
+ pthread_barrier_init(&barrier_end, NULL, num_threads + 1);
workers = malloc(num_threads * sizeof(pthread_t));
for (i = 0; i < num_threads; i++) {
pthread_create(&workers[i], NULL, render_worker, (void *)(intptr_t)i);
@@ -798,10 +1012,12 @@ gfx_cleanup(struct sys_gfx *g)
int i;
threads_running = 0;
- spin_barrier_wait(&barrier_start);
+ pthread_barrier_wait(&barrier_start);
for (i = 0; i < num_threads; i++) {
pthread_join(workers[i], NULL);
}
+ pthread_barrier_destroy(&barrier_start);
+ pthread_barrier_destroy(&barrier_end);
free(workers);
free(v_scratch);
XUngrabPointer(g->dpy, CurrentTime);
@@ -821,16 +1037,53 @@ static void
gfx_present(struct sys_gfx *g)
{
char txt[256];
+ int i, y_offset;
+
+ if (g->menu_open) {
+ for (i = 0; i < WIDTH * HEIGHT; i++) {
+ g->pixels[i] = (g->pixels[i] >> 1) & 0x7F7F7F;
+ }
+ }
XShmPutImage(g->dpy, g->win, g->gc, g->img, 0, 0, 0, 0, WIDTH, HEIGHT, False);
- if (g->in_chat) {
+
+ XSetForeground(g->dpy, g->gc, 0xFFFFFF);
+ XSetBackground(g->dpy, g->gc, 0x000000);
+
+ for (i = 0; i < chat_log_count; i++) {
+ if (chat_log[i].timer > 0.0f || g->in_chat) {
+ y_offset = HEIGHT - 45 - (chat_log_count - 1 - i) * 16;
+ if (g->font_set) {
+ Xutf8DrawImageString(g->dpy, g->win, g->font_set, g->gc,
+ 10, y_offset, chat_log[i].text, strlen(chat_log[i].text));
+ } else {
+ XDrawImageString(g->dpy, g->win, g->gc,
+ 10, y_offset, chat_log[i].text, strlen(chat_log[i].text));
+ }
+ }
+ }
+
+ if (g->menu_open) {
+ snprintf(txt, sizeof(txt), "[ PAUSED - PRESS ESC TO RESUME ]");
+ XSetForeground(g->dpy, g->gc, 0xFFFFFF);
+ XSetBackground(g->dpy, g->gc, 0x000000);
+ if (g->font_set) {
+ Xutf8DrawImageString(g->dpy, g->win, g->font_set, g->gc,
+ WIDTH / 2 - 110, HEIGHT / 2, txt, strlen(txt));
+ } else {
+ XDrawImageString(g->dpy, g->win, g->gc,
+ WIDTH / 2 - 110, HEIGHT / 2, txt, strlen(txt));
+ }
+ } else if (g->in_chat) {
snprintf(txt, sizeof(txt), "> %s_", g->chat_buf);
XSetForeground(g->dpy, g->gc, 0x00FF00);
XSetBackground(g->dpy, g->gc, 0x000000);
if (g->font_set) {
- Xutf8DrawImageString(g->dpy, g->win, g->font_set, g->gc, 10, HEIGHT - 20, txt, strlen(txt));
+ Xutf8DrawImageString(g->dpy, g->win, g->font_set, g->gc,
+ 10, HEIGHT - 20, txt, strlen(txt));
} else {
- XDrawImageString(g->dpy, g->win, g->gc, 10, HEIGHT - 20, txt, strlen(txt));
+ XDrawImageString(g->dpy, g->win, g->gc,
+ 10, HEIGHT - 20, txt, strlen(txt));
}
}
XSync(g->dpy, False);
@@ -870,7 +1123,8 @@ static float
get_ground_height(float px, float pz, float cur_y)
{
const struct col_cell *c;
- float best_below, hit_y, norm[3];
+ const struct col_triangle *t;
+ float best_below, hit_y;
uint32_t i;
int gx, gz, cx_idx, cz_idx;
@@ -892,11 +1146,11 @@ get_ground_height(float px, float pz, float cur_y)
}
c = &g_col_grid[cx_idx][cz_idx];
for (i = 0; i < c->count; i++) {
- if (c->tris[i].norm[1] < 0.45f) continue;
- if (norm[1] < 0.45f) {
+ t = &c->tris[i];
+ if (t->norm[1] < 0.45f) {
continue;
}
- if (ray_tri_intersect_y(&c->tris[i], px, pz, &hit_y)) {
+ if (ray_tri_intersect_y(t, px, pz, &hit_y)) {
if (hit_y <= cur_y + 0.50f && hit_y > best_below) {
best_below = hit_y;
}
@@ -911,8 +1165,9 @@ static void
move_and_slide(float *px, float *py, float *pz, float vx, float vz, float dt)
{
const struct col_cell *c;
+ const struct col_triangle *t;
float sphere[3][3];
- float closest[3], diff[3], norm[3], push_dir[3];
+ float closest[3], diff[3], push_dir[3];
float rad, dist_sq, dist, push, h_len, dot, step_x, step_z;
float step_h, ground_y;
int sub, iter, s, i, gx, gz, cx_idx, cz_idx;
@@ -957,15 +1212,14 @@ move_and_slide(float *px, float *py, float *pz, float vx, float vz, float dt)
c = &g_col_grid[cx_idx][cz_idx];
for (i = 0; i < (int)c->count; i++) {
- if (c->tris[i].norm[1] > 0.5f) continue;
- if (norm[1] > 0.50f) {
+ t = &c->tris[i];
+ if (t->norm[1] > 0.5f) {
continue;
}
for (s = 0; s < 3; s++) {
closest_point_triangle(sphere[s],
- c->tris[i].v0, c->tris[i].v1, c->tris[i].v2,
- closest);
+ t->v0, t->v1, t->v2, closest);
diff[0] = sphere[s][0] - closest[0];
diff[1] = sphere[s][1] - closest[1];
@@ -977,13 +1231,13 @@ move_and_slide(float *px, float *py, float *pz, float vx, float vz, float dt)
if (dist_sq < rad * rad) {
dist = sqrtf(dist_sq);
- dot = diff[0] * norm[0] +
- diff[1] * norm[1] +
- diff[2] * norm[2];
+ dot = diff[0] * t->norm[0] +
+ diff[1] * t->norm[1] +
+ diff[2] * t->norm[2];
if (dot < 0.0f || dist < 1e-4f) {
- push_dir[0] = norm[0];
- push_dir[2] = norm[2];
+ push_dir[0] = t->norm[0];
+ push_dir[2] = t->norm[2];
push = rad + dist;
} else {
push_dir[0] = diff[0] / dist;
@@ -1442,8 +1696,8 @@ flush_render_list(void)
if (render_count == 0) {
return;
}
- spin_barrier_wait(&barrier_start);
- spin_barrier_wait(&barrier_end);
+ pthread_barrier_wait(&barrier_start);
+ pthread_barrier_wait(&barrier_end);
render_count = 0;
}
@@ -1824,6 +2078,38 @@ draw_scene(struct sys_gfx *g, const struct mat4 *proj, const struct mat4 *view,
}
}
+ for (i = 0; i < MAX_PLAYERS; i++) {
+ const char *rem_skin_name;
+
+ if (!net_players[i].active || i == my_player_id) {
+ continue;
+ }
+
+ rem_skin_name = ped_lookup_model(net_players[i].skin);
+ if (rem_skin_name) {
+ struct mesh *rem_mesh;
+
+ rem_mesh = cache_get_mesh(g_cache, rem_skin_name);
+ if (rem_mesh && rem_mesh->ply_verts) {
+ struct mat4 rot_y, trans, world;
+
+ mat_identity(&rot_y);
+ rot_y.m[0][0] = cosf(net_players[i].cur_yaw);
+ rot_y.m[0][2] = sinf(net_players[i].cur_yaw);
+ rot_y.m[2][0] = -sinf(net_players[i].cur_yaw);
+ rot_y.m[2][2] = cosf(net_players[i].cur_yaw);
+
+ mat_translate(&trans, net_players[i].cur_x,
+ net_players[i].cur_y + 1.0f, net_players[i].cur_z);
+ mat_mul(&world, &trans, &rot_y);
+
+ mat_mul(&mvp, view, &world);
+ mat_mul(&mvp, proj, &mvp);
+ draw_mesh(rem_mesh, &mvp, 0);
+ }
+ }
+ }
+
flush_render_list();
}
@@ -2207,7 +2493,7 @@ scene_load(struct asset_cache *cache, const uint8_t *data, size_t sz)
}
int
-main(void)
+main(int argc, char *argv[])
{
struct sys_gfx g;
struct mat4 view;
@@ -2218,13 +2504,33 @@ main(void)
float cam_x, cam_y, cam_z;
float cam_yaw = -1.57f, cam_pitch = 0.2f, cam_dist = 3.5f;
float move_forward, move_strafe, move_x, move_z, move_len;
- float speed, ground_y, dt;
- int running, tar_fd, m_dx, m_dy, gz, gx, last_mx, last_my;
+ float speed, ground_y, dt, net_timer = 0.0f;
+ int running, tar_fd, m_dx, m_dy, gz, gx, last_mx, last_my, i;
struct stat st;
size_t ipl_sz = 0, water_sz = 0, tc_sz = 0, col_sz = 0;
const uint8_t *ipl_data, *water_data, *tc_data, *col_data;
+ char srv_ip[64] = "127.0.0.1";
+ int srv_port = NET_PORT;
+ char nick[24] = "player";
+ char *colon;
KeyCode code;
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s <ip:port> <nick>\n", argv[0]);
+ return (1);
+ }
+
+ strncpy(srv_ip, argv[1], sizeof(srv_ip) - 1);
+ srv_ip[sizeof(srv_ip) - 1] = '\0';
+ colon = strchr(srv_ip, ':');
+ if (colon) {
+ *colon = '\0';
+ srv_port = atoi(colon + 1);
+ }
+
+ strncpy(nick, argv[2], sizeof(nick) - 1);
+ nick[sizeof(nick) - 1] = '\0';
+
g_sys = &g;
gfx_init(&g);
g_cache = calloc(1, sizeof(struct asset_cache));
@@ -2289,6 +2595,8 @@ main(void)
water_tex = cache_get_tex(g_cache, "waterclear256");
sand_tex = cache_get_tex(g_cache, "sand256");
+ net_init(srv_ip, srv_port, nick);
+
XMapWindow(g.dpy, g.win);
clock_gettime(CLOCK_MONOTONIC, &t_start);
XWarpPointer(g.dpy, None, g.win, 0, 0, 0, 0, WIDTH / 2, HEIGHT / 2);
@@ -2312,12 +2620,15 @@ main(void)
(Atom)sa_event.xclient.data.l[0] == g.wm_delete) {
running = 0;
}
- if (sa_event.type == FocusIn && !g.in_chat) {
+ if (sa_event.type == FocusIn && !g.in_chat && !g.menu_open) {
XGrabPointer(g.dpy, g.win, True,
PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime);
}
- if (sa_event.type == MotionNotify) {
+ if (sa_event.type == FocusOut) {
+ memset(g.keys, 0, sizeof(g.keys));
+ }
+ if (sa_event.type == MotionNotify && !g.menu_open) {
last_mx = sa_event.xmotion.x;
last_my = sa_event.xmotion.y;
}
@@ -2330,7 +2641,17 @@ main(void)
PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime);
} else {
- running = 0;
+ g.menu_open = !g.menu_open;
+ if (g.menu_open) {
+ XUngrabPointer(g.dpy, CurrentTime);
+ memset(g.keys, 0, sizeof(g.keys));
+ } else {
+ XWarpPointer(g.dpy, None, g.win, 0, 0, 0, 0,
+ WIDTH / 2, HEIGHT / 2);
+ XGrabPointer(g.dpy, g.win, True,
+ PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
+ GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime);
+ }
}
}
if (g.in_chat) {
@@ -2347,7 +2668,10 @@ main(void)
}
if (sym == XK_Return) {
- if (strncmp(g.chat_buf, "/st ", 4) == 0) {
+ if (strcmp(g.chat_buf, "/q") == 0 ||
+ strcmp(g.chat_buf, "/quit") == 0) {
+ running = 0;
+ } else if (strncmp(g.chat_buf, "/st ", 4) == 0) {
g.game_time_hours = atof(g.chat_buf + 4);
} else if (strncmp(g.chat_buf, "/sw ", 4) == 0) {
g.game_weather = atoi(g.chat_buf + 4);
@@ -2358,6 +2682,8 @@ main(void)
if (ped_lookup_model(new_id) != NULL) {
active_skin_id = new_id;
}
+ } else if (g.chat_buf[0] != '/' && g.chat_len > 0) {
+ net_send_chat(g.chat_buf);
}
g.in_chat = 0;
XGrabPointer(g.dpy, g.win, True,
@@ -2379,7 +2705,7 @@ main(void)
g.chat_len += n;
g.chat_buf[g.chat_len] = '\0';
}
- } else {
+ } else if (!g.menu_open) {
if (code == g.key_w) g.keys['w'] = 1;
else if (code == g.key_a) g.keys['a'] = 1;
else if (code == g.key_s) g.keys['s'] = 1;
@@ -2392,7 +2718,7 @@ main(void)
}
}
}
- if (sa_event.type == KeyRelease) {
+ if (sa_event.type == KeyRelease && !g.menu_open) {
code = sa_event.xkey.keycode;
if (code == g.key_w) g.keys['w'] = 0;
else if (code == g.key_a) g.keys['a'] = 0;
@@ -2401,13 +2727,6 @@ main(void)
}
}
- m_dx = last_mx - WIDTH / 2;
- m_dy = last_my - HEIGHT / 2;
- if (m_dx != 0 || m_dy != 0) {
- XWarpPointer(g.dpy, None, g.win, 0, 0, 0, 0, WIDTH / 2, HEIGHT / 2);
- XFlush(g.dpy);
- }
-
clock_gettime(CLOCK_MONOTONIC, &t_end);
dt = (t_end.tv_sec - t_start.tv_sec) + (t_end.tv_nsec - t_start.tv_nsec) * 1e-9f;
t_start = t_end;
@@ -2420,7 +2739,29 @@ main(void)
g.game_time_hours -= 24.0f;
}
- if (!g.in_chat) {
+ for (i = 0; i < chat_log_count; i++) {
+ if (chat_log[i].timer > 0.0f) {
+ chat_log[i].timer -= dt;
+ }
+ }
+
+ net_poll(&g);
+ net_interpolate(dt);
+
+ net_timer += dt;
+ if (net_timer >= 0.033f) {
+ net_send_sync(player_x, player_y, player_z, player_yaw);
+ net_timer = 0.0f;
+ }
+
+ if (!g.in_chat && !g.menu_open) {
+ m_dx = last_mx - WIDTH / 2;
+ m_dy = last_my - HEIGHT / 2;
+ if (m_dx != 0 || m_dy != 0) {
+ XWarpPointer(g.dpy, None, g.win, 0, 0, 0, 0, WIDTH / 2, HEIGHT / 2);
+ XFlush(g.dpy);
+ }
+
cam_yaw += m_dx * 0.003f;
cam_pitch -= m_dy * 0.003f;
if (cam_pitch > 1.2f) {
@@ -2479,6 +2820,7 @@ main(void)
gfx_present(&g);
}
+ net_send_leave();
munmap(g_tar_mapped, g_tar_size);
close(tar_fd);
gfx_cleanup(&g);
diff --git a/src/server.c b/src/server.c
@@ -0,0 +1,282 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "net.h"
+
+struct server_slot {
+ struct sockaddr_in addr;
+ socklen_t addr_len;
+ time_t last_seen;
+ char name[24];
+ uint16_t skin;
+ float x, y, z;
+ float yaw;
+ int active;
+};
+
+static struct server_slot clients[MAX_PLAYERS];
+static int server_fd;
+static int max_clients = MAX_PLAYERS;
+
+static int
+find_slot_by_addr(const struct sockaddr_in *addr)
+{
+ int i;
+
+ for (i = 0; i < max_clients; i++) {
+ if (clients[i].active &&
+ clients[i].addr.sin_addr.s_addr == addr->sin_addr.s_addr &&
+ clients[i].addr.sin_port == addr->sin_port) {
+ return (i);
+ }
+ }
+ return (-1);
+}
+
+static int
+find_free_slot(void)
+{
+ int i;
+
+ for (i = 0; i < max_clients; i++) {
+ if (!clients[i].active) {
+ return (i);
+ }
+ }
+ return (-1);
+}
+
+static void
+broadcast_world_state(void)
+{
+ struct pkt_world_state pkt;
+ int i, count;
+
+ memset(&pkt, 0, sizeof(pkt));
+ pkt.hdr.magic = NET_MAGIC;
+ pkt.hdr.type = PKT_WORLD_STATE;
+
+ count = 0;
+ for (i = 0; i < max_clients; i++) {
+ if (clients[i].active) {
+ pkt.players[count].id = (uint8_t)i;
+ pkt.players[count].skin = clients[i].skin;
+ pkt.players[count].x = clients[i].x;
+ pkt.players[count].y = clients[i].y;
+ pkt.players[count].z = clients[i].z;
+ pkt.players[count].yaw = clients[i].yaw;
+ count++;
+ }
+ }
+ pkt.player_count = (uint8_t)count;
+
+ for (i = 0; i < max_clients; i++) {
+ if (clients[i].active) {
+ sendto(server_fd, &pkt,
+ sizeof(struct pkt_header) + 1 + count * sizeof(struct player_state),
+ 0, (struct sockaddr *)&clients[i].addr, clients[i].addr_len);
+ }
+ }
+}
+
+static void
+handle_packet(const uint8_t *buf, ssize_t len, const struct sockaddr_in *src_addr, socklen_t addr_len)
+{
+ const struct pkt_header *hdr;
+ struct pkt_join_ack ack;
+ int slot, i;
+ time_t now;
+
+ if (len < (ssize_t)sizeof(struct pkt_header)) {
+ return;
+ }
+ hdr = (const struct pkt_header *)buf;
+ if (hdr->magic != NET_MAGIC) {
+ return;
+ }
+
+ time(&now);
+ slot = find_slot_by_addr(src_addr);
+
+ if (hdr->type == PKT_JOIN) {
+ const struct pkt_join *pj;
+
+ if (len < (ssize_t)sizeof(struct pkt_join)) {
+ return;
+ }
+ pj = (const struct pkt_join *)buf;
+
+ if (slot < 0) {
+ slot = find_free_slot();
+ if (slot < 0) {
+ return;
+ }
+ }
+
+ clients[slot].addr = *src_addr;
+ clients[slot].addr_len = addr_len;
+ clients[slot].last_seen = now;
+ clients[slot].skin = pj->skin;
+ strncpy(clients[slot].name, pj->name, sizeof(clients[slot].name) - 1);
+ clients[slot].name[sizeof(clients[slot].name) - 1] = '\0';
+ clients[slot].active = 1;
+
+ memset(&ack, 0, sizeof(ack));
+ ack.hdr.magic = NET_MAGIC;
+ ack.hdr.type = PKT_JOIN_ACK;
+ ack.hdr.player_id = (uint8_t)slot;
+ ack.your_id = (uint8_t)slot;
+ ack.game_time = 12.0f;
+ ack.game_weather = 0;
+
+ sendto(server_fd, &ack, sizeof(ack), 0,
+ (struct sockaddr *)src_addr, addr_len);
+ printf("server: player joined [id=%d, nick=%s]\n", slot, clients[slot].name);
+ return;
+ }
+
+ if (slot < 0) {
+ return;
+ }
+ clients[slot].last_seen = now;
+
+ if (hdr->type == PKT_SYNC) {
+ const struct pkt_sync *ps;
+
+ if (len < (ssize_t)sizeof(struct pkt_sync)) {
+ return;
+ }
+ ps = (const struct pkt_sync *)buf;
+ clients[slot].x = ps->x;
+ clients[slot].y = ps->y;
+ clients[slot].z = ps->z;
+ clients[slot].yaw = ps->yaw;
+ clients[slot].skin = ps->skin;
+ } else if (hdr->type == PKT_CHAT) {
+ const struct pkt_chat *pc;
+ struct pkt_chat out_chat;
+
+ if (len < (ssize_t)sizeof(struct pkt_chat)) {
+ return;
+ }
+ pc = (const struct pkt_chat *)buf;
+ printf("[%s]: %s\n", clients[slot].name, pc->msg);
+
+ memset(&out_chat, 0, sizeof(out_chat));
+ out_chat.hdr.magic = NET_MAGIC;
+ out_chat.hdr.type = PKT_CHAT;
+ out_chat.hdr.player_id = (uint8_t)slot;
+ strncpy(out_chat.name, clients[slot].name, sizeof(out_chat.name) - 1);
+ strncpy(out_chat.msg, pc->msg, sizeof(out_chat.msg) - 1);
+
+ for (i = 0; i < max_clients; i++) {
+ if (clients[i].active) {
+ sendto(server_fd, &out_chat, sizeof(out_chat), 0,
+ (struct sockaddr *)&clients[i].addr, clients[i].addr_len);
+ }
+ }
+ } else if (hdr->type == PKT_LEAVE) {
+ clients[slot].active = 0;
+ printf("server: player left [id=%d, nick=%s]\n", slot, clients[slot].name);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_in srv_addr, cli_addr;
+ struct pollfd pfd;
+ uint8_t buf[2048];
+ ssize_t n;
+ socklen_t addr_len;
+ struct timespec t_last, t_now;
+ double elapsed;
+ int port, i;
+ time_t now;
+
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s <port> <max_players>\n", argv[0]);
+ return (1);
+ }
+
+ port = atoi(argv[1]);
+ max_clients = atoi(argv[2]);
+
+ if (port <= 0 || port > 65535) {
+ errx(1, "invalid port: %s", argv[1]);
+ }
+ if (max_clients <= 0 || max_clients > MAX_PLAYERS) {
+ errx(1, "invalid max_players (1-%d): %s", MAX_PLAYERS, argv[2]);
+ }
+
+ memset(clients, 0, sizeof(clients));
+
+ server_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (server_fd < 0) {
+ err(1, "socket");
+ }
+
+ fcntl(server_fd, F_SETFL, O_NONBLOCK);
+
+ memset(&srv_addr, 0, sizeof(srv_addr));
+ srv_addr.sin_family = AF_INET;
+ srv_addr.sin_addr.s_addr = INADDR_ANY;
+ srv_addr.sin_port = htons(port);
+
+ if (bind(server_fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0) {
+ err(1, "bind");
+ }
+
+ printf("antizona server listening on port %d (max players: %d)...\n", port, max_clients);
+
+ pfd.fd = server_fd;
+ pfd.events = POLLIN;
+
+ clock_gettime(CLOCK_MONOTONIC, &t_last);
+
+ while (1) {
+ if (poll(&pfd, 1, 5) > 0) {
+ if (pfd.revents & POLLIN) {
+ while (1) {
+ addr_len = sizeof(cli_addr);
+ n = recvfrom(server_fd, buf, sizeof(buf), 0,
+ (struct sockaddr *)&cli_addr, &addr_len);
+ if (n < 0) {
+ break;
+ }
+ handle_packet(buf, n, &cli_addr, addr_len);
+ }
+ }
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &t_now);
+ elapsed = (t_now.tv_sec - t_last.tv_sec) +
+ (t_now.tv_nsec - t_last.tv_nsec) * 1e-9;
+
+ if (elapsed >= 0.033) {
+ time(&now);
+ for (i = 0; i < max_clients; i++) {
+ if (clients[i].active && (now - clients[i].last_seen) > 5) {
+ clients[i].active = 0;
+ printf("server: timeout player [id=%d, nick=%s]\n", i, clients[i].name);
+ }
+ }
+ broadcast_world_state();
+ t_last = t_now;
+ }
+ }
+
+ close(server_fd);
+ return (0);
+}