diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/chunk.c | 66 | ||||
-rw-r--r-- | src/client.c | 104 | ||||
-rw-r--r-- | src/server.c | 71 |
3 files changed, 186 insertions, 55 deletions
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])) |