summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLizzy Fleckenstein <lizzy@vlhl.dev>2024-06-23 17:01:27 +0200
committerLizzy Fleckenstein <lizzy@vlhl.dev>2024-06-23 17:01:34 +0200
commitbf3c2eb3509076251b16ded45f8ceb4be60ae98b (patch)
tree02a0070e223022fceddd7777766eb940b71ac434
parent0c097be7c7809e86f3ebdd1bd9fe2f857919c5cd (diff)
client: handle CPKT_NODES
Signed-off-by: Lizzy Fleckenstein <lizzy@vlhl.dev>
-rw-r--r--include/chunk.h24
-rw-r--r--include/content.h33
-rw-r--r--include/vec.h64
-rw-r--r--meson.build1
-rw-r--r--src/chunk.c66
-rw-r--r--src/client.c104
-rw-r--r--src/server.c71
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]))