diff options
author | Lizzy Fleckenstein <lizzy@vlhl.dev> | 2024-06-23 17:01:27 +0200 |
---|---|---|
committer | Lizzy Fleckenstein <lizzy@vlhl.dev> | 2024-06-23 17:01:34 +0200 |
commit | bf3c2eb3509076251b16ded45f8ceb4be60ae98b (patch) | |
tree | 02a0070e223022fceddd7777766eb940b71ac434 | |
parent | 0c097be7c7809e86f3ebdd1bd9fe2f857919c5cd (diff) |
client: handle CPKT_NODES
Signed-off-by: Lizzy Fleckenstein <lizzy@vlhl.dev>
-rw-r--r-- | include/chunk.h | 24 | ||||
-rw-r--r-- | include/content.h | 33 | ||||
-rw-r--r-- | include/vec.h | 64 | ||||
-rw-r--r-- | meson.build | 1 | ||||
-rw-r--r-- | src/chunk.c | 66 | ||||
-rw-r--r-- | src/client.c | 104 | ||||
-rw-r--r-- | src/server.c | 71 |
7 files changed, 299 insertions, 64 deletions
diff --git a/include/chunk.h b/include/chunk.h new file mode 100644 index 0000000..420211f --- /dev/null +++ b/include/chunk.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 Lizzy Fleckenstein <lizzy@vlhl.dev> +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +#ifndef CHUNK_H +#define CHUNK_H + +#include <stdbool.h> +#include "vec.h" +#include "content.h" + +typedef struct { + uint32_t pitch; + box2 bounds; + node *data; +} chunk; + +chunk chunk_alloc(box2 b); +node *chunk_index(chunk c, uvec2 v); +node *chunk_index_abs(chunk c, vec2 v); +void chunk_copy(chunk dst, chunk src); +void chunk_clear(chunk c); + +#endif diff --git a/include/content.h b/include/content.h index ed8b822..664c84e 100644 --- a/include/content.h +++ b/include/content.h @@ -7,6 +7,7 @@ #include <stdint.h> #include <stdbool.h> +#include "vec.h" typedef enum { N_VALLEY_FLOWER, @@ -25,12 +26,12 @@ typedef struct { uint8_t r, g, b; } color; -static inline uint32_t color_u32(color c) +static inline 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 u32_color(uint32_t u) +static inline color color_from_u32(uint32_t u) { return (color) { (u >> 16) & 0xFF, (u >> 8) & 0xFF, u & 0xFF }; } @@ -42,12 +43,25 @@ typedef struct { color col; } node; +#define SIGHT_RANGE 10 +#define NODES_PKT_MAX (50*50) + typedef enum { - MOVE_UP = 0, - MOVE_DOWN, - MOVE_LEFT, - MOVE_RIGHT, -} move_dir; + DIR_RIGHT = 0, + DIR_UP, + DIR_LEFT, + DIR_DOWN, +} dir; + +static inline 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); + } +} typedef uint8_t fail_reason; #define ser_fail_reason ser_u8 @@ -66,8 +80,9 @@ enum { CPKT_HI = 0, // len motd CPKT_FAIL, // fail_reason CPKT_PLAYERS, // len [len name id] - CPKT_MOVE, // player_id remove x y z - CPKT_NODES, // z x y w h [node] + CPKT_MOVE, // player_id remove x y + CPKT_NODES, // box2 [node] + CPKT_RESET_MAP, }; enum { diff --git a/include/vec.h b/include/vec.h new file mode 100644 index 0000000..ce1b921 --- /dev/null +++ b/include/vec.h @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2024 Lizzy Fleckenstein <lizzy@vlhl.dev> +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +#ifndef VEC_H +#define VEC_H + +#include <stdint.h> +#include <stdbool.h> +#include "ser.h" + +#define VECFN static __attribute__((unused)) + +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#define CHKSUB(A, B) ((A) > (B) ? (A) - (B) : 0) + +#define CAST(T, V) ((T) { (V).x, (V).y }) + +#define MKVEC(V, B, S, SER) \ +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 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 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); } \ +VECFN bool deser_##B(str *r, B *b) { return deser_##V(r, &b->pos) && deser_uvec2(r, &b->size); } \ +VECFN V B##_upper(B b) { return V##_add(b.pos, CAST(V, b.size)); } \ +VECFN B B##_overlap(B a, B b) { V base = V##_max(a.pos, b.pos); \ + return (B) { base, V##_chksub(V##_min(B##_upper(a), B##_upper(b)), base) }; } \ +VECFN bool B##_empty(B b) { return b.size.x == 0 || b.size.y == 0; } \ +VECFN bool B##_contains(B b, V v) { return V##_le(b.pos, v) && V##_lt(v, B##_upper(b)); } \ +VECFN B B##_around(V center, uint32_t radius) { uint32_t diam = radius*2+1; \ + return (B) { V##_sub(center, (V) { radius, radius }), (uvec2) { diam, diam } }; } + +MKVEC(uvec2, ubox2, uint32_t, u32) +MKVEC(vec2, box2, int32_t, i32) + +#define VEC2(X, Y) ((vec2) { (X), (Y) }) +#define UVEC2(X, Y) ((uvec2) { (X), (Y) }) + +#define CUVEC2(V) ((uvec2) { (V).x, (V).y }) +#define CVEC2(V) ((vec2) { (V).x, (V).y }) + +VECFN vec2 vec2_neg(vec2 v) +{ + return VEC2(-v.x, -v.y); +} + +#undef MKVEC +#undef VECFN +#undef MIN +#undef MAX +#undef CHKSUB +#undef CAST + +#endif diff --git a/meson.build b/meson.build index 5e0c6f3..540647c 100644 --- a/meson.build +++ b/meson.build @@ -15,6 +15,7 @@ src = [ 'src/ser.c', 'src/ticker.c', 'src/sig.c', + 'src/chunk.c', ] include = 'include/' diff --git a/src/chunk.c b/src/chunk.c new file mode 100644 index 0000000..3212194 --- /dev/null +++ b/src/chunk.c @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2024 Lizzy Fleckenstein <lizzy@vlhl.dev> +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +#include <stdlib.h> +#include <string.h> +#include "chunk.h" + +node *chunk_col(chunk c, uint32_t x) +{ + return &c.data[c.pitch*x]; +} + +node *chunk_index(chunk c, uvec2 v) +{ + return &chunk_col(c, v.x)[v.y]; +} + +node *chunk_index_abs(chunk c, vec2 v) +{ + if (box2_contains(c.bounds, v)) + return chunk_index(c, CUVEC2(vec2_sub(v, c.bounds.pos))); + else + return NULL; +} + +chunk chunk_sub(chunk c, box2 area) +{ + return (chunk) { + .pitch = c.pitch, + .bounds = area, + .data = chunk_index(c, CUVEC2(vec2_sub(area.pos, c.bounds.pos))), + }; +} + +chunk chunk_alloc(box2 b) +{ + return (chunk) { + .pitch = b.size.y, + .bounds = b, + .data = calloc(b.size.x * b.size.y, sizeof(node)), + }; +} + +void chunk_copy(chunk dst, chunk src) +{ + box2 area = box2_overlap(dst.bounds, src.bounds); + if (box2_empty(area)) + return; + + chunk d = chunk_sub(dst, area); + chunk s = chunk_sub(src, area); + + if (d.pitch == area.size.y && s.pitch == area.size.y) + memcpy(d.data, s.data, area.size.x * area.size.y * sizeof(node)); + else for (uint32_t x = 0; x < area.size.x; x++) + memcpy(chunk_col(d, x), chunk_col(s, x), area.size.y * sizeof(node)); +} + +void chunk_clear(chunk c) +{ + if (c.bounds.size.y == c.pitch) + memset(c.data, 0, c.bounds.size.x * c.bounds.size.y * sizeof(node)); + else for (uint32_t x = 0; x < c.bounds.size.x; x++) + memset(chunk_col(c, x), 0, c.bounds.size.y * sizeof(node)); +} diff --git a/src/client.c b/src/client.c index 0ab1b53..79a012d 100644 --- a/src/client.c +++ b/src/client.c @@ -5,12 +5,14 @@ #include <poll.h> #include <errno.h> #include <ctype.h> +#include <stdint.h> #include "str.h" #include "ser.h" #include "net.h" #include "ticker.h" #include "sig.h" #include "content.h" +#include "chunk.h" typedef struct { str name; @@ -25,6 +27,9 @@ typedef struct { uint64_t self_id; array(player) players; str server_motd; + chunk map; + node *map_swap; + vec2 player_pos; } client; void gfx_alt_buffer(bool enable) @@ -35,6 +40,16 @@ void gfx_alt_buffer(bool enable) printf("\e[?1049l\e[?25h"); } +void gfx_clear_effects() +{ + printf("\e[0m"); +} + +void gfx_set_color(color c, bool bg) +{ + printf("\e[%u;2;%u;%u;%um", bg ? 48 : 38, c.r, c.g, c.b); +} + void gfx_clear() { printf("\e[2J"); @@ -47,6 +62,21 @@ void gfx_render(client *c, uint64_t dtime) printf("%.*s\n", PSTR(c->server_motd)); for (size_t i = 0; i < c->players.len; i++) 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(" "); + } + } + gfx_clear_effects(); + printf("\n"); + } fflush(stdout); } @@ -72,6 +102,7 @@ void client_exit(client *c, int ret) peer_free(&c->conn); tcsetattr(STDIN_FILENO, TCSANOW, &c->oldtio); gfx_alt_buffer(false); + fflush(stdout); exit(ret); } @@ -85,22 +116,72 @@ void handle_input(client *c, char ch) } } -bool handle_players(str *w, client *c) +void map_set_center(client *c, vec2 center) +{ + chunk old = c->map; + + c->map.data = c->map_swap; + c->map.bounds.pos = center; + 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; + + node nodes[ch.bounds.size.x * ch.bounds.size.y]; + ch.pitch = ch.bounds.size.y; + ch.data = nodes; + + for (uint32_t x = 0; x < ch.bounds.size.x; x++) + for (uint32_t y = 0; y < ch.bounds.size.y; y++) + if (!deser_node(pkt, chunk_index(ch, UVEC2(x, y)))) return false; + + chunk_copy(c->map, ch); + return true; +} + +bool handle_reset_map(str *pkt, client *c) +{ + (void) pkt; + chunk_clear(c->map); + return true; +} + +bool handle_players(str *pkt, client *c) { free_players(c); uint16_t len; - if (!deser_u16(w, &len)) - return false; + if (!deser_u16(pkt, &len)) return false; c->players.data = malloc(sizeof *c->players.data * len); for (c->players.len = 0; c->players.len < len; c->players.len++) { player *p = &c->players.data[c->players.len]; - if (!deser_str(w, &p->name)) - return false; - if (!deser_u64(w, &p->id)) - return false; + if (!deser_str(pkt, &p->name)) return false; + if (!deser_u64(pkt, &p->id)) return false; if (str_eq(p->name, c->name)) c->self_id = p->id; p->name = str_clone(p->name); @@ -111,8 +192,7 @@ bool handle_players(str *w, client *c) bool handle_hi(str *w, client *c) { str motd; - if (!deser_str(w, &motd)) - return false; + if (!deser_str(w, &motd)) return false; c->server_motd = str_clone(motd); return true; @@ -127,6 +207,8 @@ bool handle_pkt(client *c, str pkt) switch (type) { case CPKT_HI: return handle_hi(&pkt, c); 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); default: return false; } } @@ -163,6 +245,7 @@ int main(int argc, char **argv) str server_name = S("server"); peer_init(&c.conn, socket, &server_name); + setvbuf(stdout, NULL, _IOFBF, 0); gfx_alt_buffer(true); SEND_PKT(c.conn, SPKT_HI, @@ -170,6 +253,9 @@ int main(int argc, char **argv) ser_str(&pkt, c.pass); ) + c.map = chunk_alloc(box2_around(VEC2(0, 0), SIGHT_RANGE)); + c.map_swap = malloc(c.map.bounds.size.x * c.map.bounds.size.y * sizeof(node)); + ticker t; ticker_init(&t, NANOS/60); diff --git a/src/server.c b/src/server.c index adbbe80..45d8384 100644 --- a/src/server.c +++ b/src/server.c @@ -20,6 +20,7 @@ #include "str.h" #include "ser.h" #include "sig.h" +#include "chunk.h" typedef enum { MAP_VALLEY, @@ -33,19 +34,15 @@ typedef struct { const char *filename; str name; map_type type; - uint32_t width; - uint32_t height; - int32_t off_x; - int32_t off_y; int8_t z; // caves: -1 - node *nodes; + chunk chunk; } map; typedef struct { bool auth; str name; uint64_t id; - int32_t x, y; + vec2 pos; int8_t z; peer conn; } player; @@ -59,20 +56,13 @@ typedef struct { uint64_t entity_id; } game; -node *map_local_node(map *m, int32_t x, int32_t y) -{ - if (x < 0 || y < 0 || (uint32_t) x >= m->width || (uint32_t) y >= m->height) - return NULL; - return &m->nodes[x*m->height+y]; -} - -node *map_node(game *g, int32_t x, int32_t y, int8_t z) +node *map_node(game *g, vec2 p, int8_t z) { for (size_t i = 0; i < g->maps.len; i++) { map *m = &g->maps.data[i]; if (m->z != z) continue; - node *n = map_local_node(m, x - m->off_x, y - m->off_y); + node *n = chunk_index_abs(m->chunk, p); if (n == NULL || !n->present) continue; return n; @@ -80,17 +70,19 @@ node *map_node(game *g, int32_t x, int32_t y, int8_t z) return NULL; } -void map_load_node(map *m, uint32_t x, uint32_t y, uint32_t color) +void map_load_node(map *m, uvec2 v, color col) { - node *n = map_local_node(m, x, y); - n->present = color != 0xffffff; + uint32_t ucol = color_to_u32(col); + + node *n = chunk_index(m->chunk, v); + n->present = ucol != 0xffffff; if (!n->present) return; n->z = 0; - n->col = u32_color(color); - switch (color) { + n->col = col; + switch (ucol) { case 0x00880d: n->type = N_GRASS; break; // TODO: color case 0x595959: n->type = N_ROCK; n->z = 1; break; case 0x484848: n->type = N_ROCK; n->z = 2; break; @@ -104,7 +96,7 @@ void map_load_node(map *m, uint32_t x, uint32_t y, uint32_t color) case 0x016300: n->type = N_BIG_TREE; break; default: fprintf(stderr, "invalid color in map %.*s at %"PRIu32" %"PRIu32": %06x\n", - PSTR(m->name), x, y, color); + PSTR(m->name), v.x, v.y, ucol); n->present = false; break; } @@ -136,12 +128,10 @@ bool map_load(map *m) png_uint_32 height = png_get_image_height(png, info); png_byte color_type = png_get_color_type(png, info); - TRY(width == m->width, "%s: width mismatch\n", m->filename) - TRY(height == m->height, "%s: height mismatch\n", m->filename) + TRY(width == m->chunk.bounds.size.x, "%s: width mismatch\n", m->filename) + TRY(height == m->chunk.bounds.size.y, "%s: height mismatch\n", m->filename) TRY(color_type == PNG_COLOR_TYPE_RGB, "%s: color type is not RGB\n", m->filename) - m->nodes = malloc(m->width * m->height * sizeof *m->nodes); - // uint32_t colors[100] = {0}; png_uint_32 pitch = png_get_rowbytes(png, info); @@ -152,8 +142,6 @@ bool map_load(map *m) for (png_uint_32 x = 0; x < width; x++) { png_bytep p = &row[x*3]; - uint32_t col = color_u32((color) { p[0], p[1], p[2] }); - /* for (size_t i = 0; i < 100; i++) { if (colors[i] == color) { @@ -165,7 +153,7 @@ bool map_load(map *m) } }*/ - map_load_node(m, x, y, col); + map_load_node(m, UVEC2(x, y), (color) { p[0], p[1], p[2] }); } } @@ -209,17 +197,13 @@ void send_players(game *g) } // send_nudes when -void send_nodes(player *p, game *g, int8_t z, int32_t x, int32_t y, uint32_t w, uint32_t h) +void send_nodes(player *p, game *g, box2 bounds, int8_t z) { SEND_PKT(p->conn, CPKT_NODES, - ser_i8(&pkt, z); - ser_i32(&pkt, x); - ser_i32(&pkt, y); - ser_u32(&pkt, w); - ser_u32(&pkt, h); - for (uint32_t xi = 0; xi < w; xi++) - for (uint32_t yi = 0; yi < w; yi++) - ser_node(&pkt, map_node(g, x+xi, y+yi, z)); + ser_box2(&pkt, bounds); + for (uint32_t x = 0; x < bounds.size.x; x++) + for (uint32_t y = 0; y < bounds.size.y; y++) + ser_node(&pkt, map_node(g, vec2_add(bounds.pos, VEC2(x, y)), z)); ) } @@ -246,7 +230,7 @@ void game_exit(game *g, int ret) for (size_t i = 0; i < g->maps.len; i++) { free(g->maps.data[i].name.data); - free(g->maps.data[i].nodes); + free(g->maps.data[i].chunk.data); } free(g->maps.data); @@ -281,14 +265,13 @@ bool handle_hi(str pkt, player *p, game *g) p->auth = true; p->name = str_clone(name); - p->x = p->y = 0; + p->pos.x = p->pos.y = 0; p->z = 0; SEND_PKT(p->conn, CPKT_HI, ser_str(&pkt, g->motd);) send_players(g); - uint32_t range = 10; - send_nodes(p, g, p->z, p->x-range, p->y-range, range*2, range*2); + send_nodes(p, g, box2_around(p->pos, SIGHT_RANGE), p->z); return true; } @@ -325,12 +308,8 @@ int main() .filename = "assets/map_valley.png", .name = str_clone(S("Valley")), .type = MAP_VALLEY, - .off_x = -50, - .off_y = -50, .z = 0, - .width = 100, - .height = 100, - .nodes = NULL, + .chunk = chunk_alloc((box2) { { -50, -50 }, { 100, 100 } }), }; if (!map_load(&g.maps.data[0])) |