summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/chunk.c66
-rw-r--r--src/client.c104
-rw-r--r--src/server.c71
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]))