antizona

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 699c6de2cacb555259244389e72d2baa57e69108
parent 97b5b1dab80beed0681d4d9be6986d16f3eb8ac0
Author: averagecoder <averagecoder@noreply.codeberg.org>
Date:   Mon, 24 Aug 2026 01:32:18 +0300

feat: add networking

Diffstat:
MMakefile | 6+++++-
Msrc/build.c | 48++++++++++++++++++++++++++++++++++++++++++------
Asrc/net.h | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/render.c | 488+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Asrc/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); +}