From 3be565f4e8ee1610aae5b0945185b4012f026c87 Mon Sep 17 00:00:00 2001 From: Lizzy Fleckenstein Date: Sun, 23 Jun 2024 20:07:44 +0200 Subject: implement movement Signed-off-by: Lizzy Fleckenstein --- include/content.h | 106 +++++++++++++++++++++++++++++++++--------- include/vec.h | 6 ++- src/client.c | 137 ++++++++++++++++++++++++++++++++++++++++-------------- src/server.c | 52 ++++++++++++--------- 4 files changed, 221 insertions(+), 80 deletions(-) diff --git a/include/content.h b/include/content.h index 664c84e..9d35dc8 100644 --- a/include/content.h +++ b/include/content.h @@ -7,9 +7,13 @@ #include #include +#include "ser.h" #include "vec.h" -typedef enum { +#define SIGHT_RANGE 10 +#define PKT_NODES_MAX (50*50) + +typedef enum : uint16_t { N_VALLEY_FLOWER, N_MOUNTAIN_FLOWER, N_BIG_TREE, @@ -22,16 +26,31 @@ typedef enum { N_SAND, } node_type; +#define ser_node_type ser_u16 +#define deser_node_type deser_u16 + typedef struct { uint8_t r, g, b; } color; -static inline uint32_t color_to_u32(color c) +[[maybe_unused]] static void ser_color(strbuf *w, color c) +{ + ser_u8(w, c.r); + ser_u8(w, c.g); + ser_u8(w, c.b); +} + +[[maybe_unused]] static bool deser_color(str *r, color *c) +{ + return deser_u8(r, &c->r) && deser_u8(r, &c->g) && deser_u8(r, &c->b); +} + +[[maybe_unused]] static uint32_t color_to_u32(color c) { return ((uint32_t) c.b) | (((uint32_t) c.g) << 8) | (((uint32_t) c.r) << 16); } -static inline color color_from_u32(uint32_t u) +[[maybe_unused]] static color color_from_u32(uint32_t u) { return (color) { (u >> 16) & 0xFF, (u >> 8) & 0xFF, u & 0xFF }; } @@ -43,51 +62,92 @@ typedef struct { color col; } node; -#define SIGHT_RANGE 10 -#define NODES_PKT_MAX (50*50) +[[maybe_unused]] static void ser_node(strbuf *w, node *n) +{ + bool present = n != NULL && n->present; + ser_bool(w, present); + if (!present) + return; + ser_node_type(w, n->type); + ser_i8(w, n->z); + ser_color(w, n->col); +} + +[[maybe_unused]] static bool deser_node(str *r, node *n) +{ + if (!deser_bool(r, &n->present)) return false; + if (!n->present) return true; + + return deser_node_type(r, &n->type) + && deser_i8(r, &n->z) + && deser_color(r, &n->col); +} -typedef enum { +typedef enum : uint8_t { DIR_RIGHT = 0, DIR_UP, DIR_LEFT, DIR_DOWN, } dir; -static inline vec2 dir_to_vec2(dir d) +#define ser_dir ser_u8 +#define deser_dir deser_u8 + +[[maybe_unused]] static vec2 dir_to_vec2(dir d) { switch (d) { - case DIR_RIGHT: return VEC2(0, 1); - case DIR_UP: return VEC2(-1, 0); - case DIR_LEFT: return VEC2(0, -1); - case DIR_DOWN: return VEC2(1, 0); + case DIR_RIGHT: return VEC2(1, 0); + case DIR_UP: return VEC2(0, -1); + case DIR_LEFT: return VEC2(-1, 0); + case DIR_DOWN: return VEC2(0, 1); + default: return VEC2(0, 0); } } -typedef uint8_t fail_reason; +typedef enum : uint8_t { + FAIL_WRONG_PASS = 0, + FAIL_ALREADY_ONLINE, +} fail_reason; + #define ser_fail_reason ser_u8 #define deser_fail_reason deser_u8 -enum { - FAIL_WRONG_PASS = 0, - FAIL_ALREADY_ONLINE, -}; +typedef uint64_t entity_id; +#define ser_entity_id ser_u64 +#define deser_entity_id deser_u64 -typedef uint16_t pkt_type; -#define ser_pkt_type ser_u16 -#define deser_pkt_type deser_u16 +typedef enum : uint16_t { + ENTITY_PLAYER +} entity_type; + +#define ser_entity_type ser_u16 +#define deser_entity_type deser_u16 + +typedef enum : uint8_t { + ENTITY_ADD, // type + ENTITY_REMOVE, + ENTITY_MOVE, // x y +} entity_cmd; + +#define ser_entity_cmd ser_u8 +#define deser_entity_cmd deser_u8 -enum { +enum : uint16_t { CPKT_HI = 0, // len motd CPKT_FAIL, // fail_reason CPKT_PLAYERS, // len [len name id] - CPKT_MOVE, // player_id remove x y + CPKT_ENTITY, // len [entity_id command] CPKT_NODES, // box2 [node] CPKT_RESET_MAP, }; -enum { +enum : uint16_t { SPKT_HI = 0, // len name len password - SPKT_MOVE, // move_dir + SPKT_MOVE, // dir }; +typedef uint16_t pkt_type; +#define ser_pkt_type ser_u16 +#define deser_pkt_type deser_u16 + #endif diff --git a/include/vec.h b/include/vec.h index ce1b921..d2d9e31 100644 --- a/include/vec.h +++ b/include/vec.h @@ -9,7 +9,7 @@ #include #include "ser.h" -#define VECFN static __attribute__((unused)) +#define VECFN [[maybe_unused]] static #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) @@ -22,12 +22,16 @@ typedef struct { S x; S y; } V; \ typedef struct { V pos; uvec2 size; } B; \ VECFN V V##_add(V a, V b) { return (V) { a.x+b.x, a.y+b.y }; } \ VECFN V V##_sub(V a, V b) { return (V) { a.x-b.x, a.y-b.y }; } \ +VECFN V V##_mul(V v, S s) { return (V) { v.x*s, v.y*s }; } \ +VECFN V V##_div(V v, S s) { return (V) { v.x/s, v.y/s }; } \ VECFN uvec2 V##_chksub(V a, V b) { return (uvec2) { CHKSUB(a.x, b.x), CHKSUB(a.y, b.y) }; } \ VECFN V V##_min(V a, V b) { return (V) { MIN(a.x, b.x), MIN(a.y, b.y) }; } \ VECFN V V##_max(V a, V b) { return (V) { MAX(a.x, b.x), MAX(a.y, b.y) }; } \ VECFN bool V##_eq(V a, V b) { return a.x == b.x && a.y == b.y; } \ VECFN bool V##_le(V a, V b) { return a.x <= b.x && a.y <= b.y; } \ VECFN bool V##_lt(V a, V b) { return a.x < b.x && a.y < b.y; } \ +VECFN V V##_zero() { return (V) { 0, 0 }; } \ +VECFN bool V##_iszero(V v) { return V##_eq(v, V##_zero()); } \ VECFN void ser_##V(strbuf *w, V v) { ser_##SER(w, v.x); ser_##SER(w, v.y); } \ VECFN bool deser_##V(str *r, V *v) { return deser_##SER(r, &v->x) && deser_##SER(r, &v->y); } \ VECFN void ser_##B(strbuf *w, B b) { ser_##V(w, b.pos); ser_uvec2(w, b.size); } \ diff --git a/src/client.c b/src/client.c index 79a012d..207dc97 100644 --- a/src/client.c +++ b/src/client.c @@ -16,7 +16,7 @@ typedef struct { str name; - uint64_t id; + entity_id id; } player; typedef struct { @@ -24,7 +24,7 @@ typedef struct { struct termios oldtio; str name; str pass; - uint64_t self_id; + entity_id self_id; array(player) players; str server_motd; chunk map; @@ -55,6 +55,46 @@ void gfx_clear() printf("\e[2J"); } +void gfx_render_node(client *c, vec2 pos, node *n) +{ + if (!n->present) { + gfx_set_color(color_from_u32(0x555555), true); + gfx_set_color(color_from_u32(0xffffff), false); + printf(" ?"); + return; + } + + gfx_set_color(n->col, true); + + if (vec2_eq(pos, c->player_pos)) { + printf("🥺"); + return; + } + + // TODO: persistence + switch (n->type) { + case N_GRASS: if (rand() % 10 == 0) { + gfx_set_color(color_from_u32(0x1c571e), false); + printf(" M"); + return; + } break; + case N_WATER: if (rand() % 10 == 0) { + gfx_set_color(color_from_u32(0x0d79de), false); + printf(" ~"); + return; + } break; + case N_SAND: if (rand() % 15 == 0) { + gfx_set_color(color_from_u32(0xe3a112), false); + printf(" ~"); + return; + } break; + default: break; + } + + printf(" "); +} + + void gfx_render(client *c, uint64_t dtime) { gfx_clear(); @@ -64,15 +104,9 @@ void gfx_render(client *c, uint64_t dtime) printf("%.*s\n", PSTR(c->players.data[i].name)); for (size_t y = 0; y < c->map.bounds.size.y; y++) { for (size_t x = 0; x < c->map.bounds.size.y; x++) { - node *n = chunk_index(c->map, UVEC2(x, y)); - if (!n->present) { - gfx_set_color(color_from_u32(0x555555), true); - gfx_set_color(color_from_u32(0xffffff), false); - printf("??"); - } else { - gfx_set_color(n->col, true); - printf(" "); - } + uvec2 off = UVEC2(x, y); + vec2 p = vec2_add(c->map.bounds.pos, CVEC2(off)); + gfx_render_node(c, p, chunk_index(c->map, off)); } gfx_clear_effects(); printf("\n"); @@ -107,48 +141,50 @@ void client_exit(client *c, int ret) exit(ret); } -void handle_input(client *c, char ch) +dir dir_from_key(char ch) { - ch = tolower(ch); switch (ch) { - case 'q': client_exit(c, EXIT_SUCCESS); break; - default: break; + case 'w': return DIR_UP; + case 'a': return DIR_LEFT; + case 's': return DIR_DOWN; + case 'd': return DIR_RIGHT; + default: return 4; } } +void handle_input(client *c, char ch) +{ + ch = tolower(ch); + + dir move; + if ((move = dir_from_key(ch)) != 4) + SEND_PKT(c->conn, SPKT_MOVE, ser_dir(&pkt, move);) +} + void map_set_center(client *c, vec2 center) { + vec2 pos = vec2_sub(center, CVEC2(uvec2_div(c->map.bounds.size, 2))); + + if (vec2_eq(c->map.bounds.pos, pos)) + return; + chunk old = c->map; c->map.data = c->map_swap; - c->map.bounds.pos = center; + c->map.bounds.pos = pos; c->map_swap = old.data; chunk_clear(c->map); chunk_copy(c->map, old); } -bool deser_color(str *r, color *c) -{ - return deser_u8(r, &c->r) && deser_u8(r, &c->g) && deser_u8(r, &c->b); -} - -bool deser_node(str *r, node *n) -{ - if (!deser_bool(r, &n->present)) return false; - if (!n->present) return true; - - return deser_i8(r, &n->z) - && deser_color(r, &n->col); -} - bool handle_nodes(str *pkt, client *c) { chunk ch; if (!deser_vec2(pkt, &ch.bounds.pos)) return false; if (!deser_uvec2(pkt, &ch.bounds.size)) return false; - if (ch.bounds.size.x * ch.bounds.size.y > NODES_PKT_MAX) return false; + if (ch.bounds.size.x * ch.bounds.size.y > PKT_NODES_MAX) return false; node nodes[ch.bounds.size.x * ch.bounds.size.y]; ch.pitch = ch.bounds.size.y; @@ -181,7 +217,7 @@ bool handle_players(str *pkt, client *c) for (c->players.len = 0; c->players.len < len; c->players.len++) { player *p = &c->players.data[c->players.len]; if (!deser_str(pkt, &p->name)) return false; - if (!deser_u64(pkt, &p->id)) return false; + if (!deser_entity_id(pkt, &p->id)) return false; if (str_eq(p->name, c->name)) c->self_id = p->id; p->name = str_clone(p->name); @@ -189,15 +225,47 @@ bool handle_players(str *pkt, client *c) return true; } -bool handle_hi(str *w, client *c) +bool handle_hi(str *pkt, client *c) { str motd; - if (!deser_str(w, &motd)) return false; + if (!deser_str(pkt, &motd)) return false; c->server_motd = str_clone(motd); return true; } +bool handle_entity(str *pkt, client *c) +{ + uint16_t num; + if (!deser_u16(pkt, &num)) return false; + for (uint16_t i = 0; i < num; i++) { + entity_id id; + entity_cmd cmd; + + if (!deser_entity_id(pkt, &id)) return false; + if (!deser_entity_cmd(pkt, &cmd)) return false; + + switch (cmd) { + case ENTITY_ADD: { + entity_type type; + if (!deser_entity_type(pkt, &type)) return false; + } break; + case ENTITY_REMOVE: break; + case ENTITY_MOVE: + vec2 pos; + if (!deser_vec2(pkt, &pos)) return false; + + if (id == c->self_id) + map_set_center(c, c->player_pos = pos); + + break; + default: return false; + } + } + + return true; +} + bool handle_pkt(client *c, str pkt) { pkt_type type; @@ -209,6 +277,7 @@ bool handle_pkt(client *c, str pkt) case CPKT_PLAYERS: return handle_players(&pkt, c); case CPKT_RESET_MAP: return handle_reset_map(&pkt, c); case CPKT_NODES: return handle_nodes(&pkt, c); + case CPKT_ENTITY: return handle_entity(&pkt, c); default: return false; } } diff --git a/src/server.c b/src/server.c index 45d8384..30b5676 100644 --- a/src/server.c +++ b/src/server.c @@ -41,7 +41,7 @@ typedef struct { typedef struct { bool auth; str name; - uint64_t id; + entity_id id; vec2 pos; int8_t z; peer conn; @@ -53,7 +53,7 @@ typedef struct { str passphrase; array(map) maps; arraybuf(player) players; - uint64_t entity_id; + entity_id next_entity; } game; node *map_node(game *g, vec2 p, int8_t z) @@ -163,23 +163,6 @@ bool map_load(map *m) #undef TRY } -void ser_color(strbuf *w, color c) -{ - ser_u8(w, c.r); - ser_u8(w, c.g); - ser_u8(w, c.b); -} - -void ser_node(strbuf *w, node *n) -{ - bool present = n != NULL && n->present; - ser_bool(w, present); - if (!present) - return; - ser_i8(w, n->z); - ser_color(w, n->col); -} - void send_players(game *g) { for (size_t i = 0; i < g->players.len; i++) { @@ -190,7 +173,7 @@ void send_players(game *g) player *p = &g->players.data[i]; if (!p->auth) continue; ser_str(&pkt, p->name); - ser_u64(&pkt, p->id); + ser_entity_id(&pkt, p->id); } ) } @@ -276,6 +259,30 @@ bool handle_hi(str pkt, player *p, game *g) return true; } +bool handle_move(str pkt, player *p, [[maybe_unused]] game *g) +{ + dir d; + if (!deser_dir(&pkt, &d)) return false; + + vec2 v = dir_to_vec2(d); + if (vec2_iszero(v)) + return false; + + // yolo + p->pos = vec2_add(v, p->pos); + SEND_PKT(p->conn, CPKT_ENTITY, + ser_u16(&pkt, 1); + ser_entity_id(&pkt, p->id); + ser_entity_cmd(&pkt, ENTITY_MOVE); + ser_vec2(&pkt, p->pos); + ) + + // TODO: only send newly visible nodes + send_nodes(p, g, box2_around(p->pos, SIGHT_RANGE), p->z); + + return true; +} + bool handle_pkt(str pkt, player *p, game *g) { pkt_type type; @@ -287,6 +294,7 @@ bool handle_pkt(str pkt, player *p, game *g) switch (type) { case SPKT_HI: return handle_hi(pkt, p, g); + case SPKT_MOVE: return handle_move(pkt, p, g); default: return false; } } @@ -298,7 +306,7 @@ int main() game g = {0}; g.accept_fd = -1; - g.entity_id = 1; + g.next_entity = 1; g.motd = str_clone(S("Welcome to test server")); g.passphrase = str_clone(S("")); @@ -359,7 +367,7 @@ int main() printf("new player\n"); player *p = ARR_APPEND(g.players); p->auth = false; - p->id = g.entity_id++; + p->id = g.next_entity++; p->name = S("(unauthenticated)"); peer_init(&p->conn, socket, &p->name); } -- cgit v1.2.3