From 95283d70eee749d984b2ca07c0b61d39f9bb350d Mon Sep 17 00:00:00 2001 From: Lizzy Fleckenstein Date: Sat, 13 Jul 2024 12:47:36 +0200 Subject: implement entities Signed-off-by: Lizzy Fleckenstein --- include/array.h | 2 + include/content.h | 15 +++- src/client.c | 234 +++++++++++++++++++++++++++++++++--------------------- src/server.c | 187 +++++++++++++++++++++++++++++++++++-------- 4 files changed, 311 insertions(+), 127 deletions(-) diff --git a/include/array.h b/include/array.h index 5f81798..9affdde 100644 --- a/include/array.h +++ b/include/array.h @@ -6,6 +6,8 @@ #define ARRAY_H #include +#include +#include #define len(X) (sizeof X / sizeof *X) #define array(T) struct { size_t len; T *data; } diff --git a/include/content.h b/include/content.h index 16118f6..ef27e22 100644 --- a/include/content.h +++ b/include/content.h @@ -14,9 +14,7 @@ #define PKT_NODES_MAX (50*50) typedef enum : uint16_t { - N_VALLEY_FLOWER = 0, - N_MOUNTAIN_FLOWER, - N_BIG_TREE, + N_BIG_TREE = 0, N_NEEDLE_TREE, N_ROCK, N_WATER, @@ -133,6 +131,7 @@ typedef uint64_t entity_id; typedef enum : uint16_t { ENTITY_PLAYER = 0, + ENTITY_FLOWER, } entity_type; #define ser_entity_type ser_u16 @@ -142,11 +141,21 @@ typedef enum : uint8_t { ENTITY_ADD = 0, // type ENTITY_REMOVE, ENTITY_MOVE, // x y + ENTITY_CMD_COUNT, } entity_cmd; #define ser_entity_cmd ser_u8 #define deser_entity_cmd deser_u8 +typedef struct { + entity_id id; + entity_type type; + vec2 pos; + union { + flower_type flower; + }; +} entity; + enum : uint16_t { CPKT_HI = 0, // len motd CPKT_FAIL, // fail_reason diff --git a/src/client.c b/src/client.c index 78511c6..679fcd4 100644 --- a/src/client.c +++ b/src/client.c @@ -26,27 +26,35 @@ typedef struct { str pass; entity_id self_id; array(player) players; + arraybuf(entity) entities; str server_motd; chunk map; node *map_swap; - vec2 player_pos; - char *termbuf_buf; - size_t termbuf_size; - FILE *termbuf; + char *frame_buf; + size_t frame_size; + FILE *frame; } client; -void gfx_alt_buffer(bool enable) +player *player_from_id(client *c, entity_id id) { - if (enable) - printf("\e[?1049h\e[?25l"); - else - printf("\e[?1049l\e[?25h"); - fflush(stdout); + for (size_t i = 0; i < c->players.len; i++) + if (c->players.data[i].id == id) + return &c->players.data[i]; + return NULL; } -void gfx_clear_effects(client *c) +entity *entity_from_id(client *c, entity_id id) +{ + for (size_t i = 0; i < c->entities.len; i++) + if (c->entities.data[i].id == id) + return &c->entities.data[i]; + return NULL; +} + +void gfx_alt_buffer(bool enable) { - fprintf(c->termbuf, "\e[0m"); + str out = enable ? S("\e[?1049h\e[?25l") : S("\e[?1049l\e[?25h"); + write(STDOUT_FILENO, out.data, out.len); } typedef struct { @@ -55,83 +63,56 @@ typedef struct { color old; } color_layer; +void gfx_clear_effects(client *c) +{ + fprintf(c->frame, "\e[0m"); +} + void gfx_set_color(client *c, color_layer *l, color col) { if (l->has_old && l->old.r == col.r && l->old.g == col.g && l->old.b == col.b) return; - fprintf(c->termbuf, "\e[%u;2;%u;%u;%um", l->bg ? 48 : 38, col.r, col.g, col.b); + fprintf(c->frame, "\e[%u;2;%u;%u;%um", l->bg ? 48 : 38, col.r, col.g, col.b); l->has_old = true; l->old = col; } void gfx_clear(client *c) { - fprintf(c->termbuf, "\e[2J"); + fprintf(c->frame, "\e[2J"); } #define C color_from_u32 -#define DISPLAY_BG(X) (node_display) { X, { 0 }, NULL } -#define DISPLAY(...) (node_display) { __VA_ARGS__ } +#define DISPLAY(...) (tile_display) { __VA_ARGS__ } +#define DISPLAY_BG(X) (tile_display) { X, { 0 }, NULL } #define DISPLAY_ERR DISPLAY(C(0xff0000), C(0x000000), " ?") typedef struct { color bg; color fg; char *texture; -} node_display; - -bool gfx_rock_bg(node *n, color *c) -{ - switch (n->z) { - case 1: *c = C(0x595959); return true; - case 2: *c = C(0x484848); return true; - case 3: *c = C(0x373737); return true; - default: return false; - } -} - -color gfx_grass_bg(vec2 pos) -{ - return C(0x00880d); -} +} tile_display; bool gfx_water_wave(vec2 pos) { return true; } -node_display gfx_render_node(client *c, vec2 pos, node *n) +tile_display gfx_render_node(client *c, vec2 pos, node *n) { if (!n->present) return DISPLAY(C(0x555555), C(0xffffff), " ?"); switch (n->type) { - case N_VALLEY_FLOWER: - case N_MOUNTAIN_FLOWER: { - node_display d = {0}; - if (n->type == N_VALLEY_FLOWER) - d.bg = gfx_grass_bg(pos); - else if (!gfx_rock_bg(n, &d.bg)) - return DISPLAY_ERR; - switch (n->variant) { - case FLOWER_ROSE: d.texture = "🌹"; break; - case FLOWER_HIBISCUS: d.texture = "🌺"; break; - case FLOWER_SUNFLOWER: d.texture = "🌻"; break; - case FLOWER_DANDELION: d.texture = "🌼"; break; - case FLOWER_TULIP: d.texture = "🌷"; break; - default: return DISPLAY_ERR; - } - return d; - } - case N_ROCK: { - node_display d = {0}; - if (!gfx_rock_bg(n, &d.bg)) - return DISPLAY_ERR; - return d; + case N_ROCK: switch (n->z) { + case 1: return DISPLAY_BG(C(0x595959)); + case 2: return DISPLAY_BG(C(0x484848)); + case 3: return DISPLAY_BG(C(0x373737)); + default: return DISPLAY_ERR; } case N_WATER: return DISPLAY(C(0x00c6ff), C(0x0d79de), n->variant && gfx_water_wave(pos) ? " ~" : NULL); - case N_GRASS: return DISPLAY(gfx_grass_bg(pos), C(0x1c571e), n->variant ? " M" : NULL); + case N_GRASS: return DISPLAY(C(0x00880d), C(0x1c571e), n->variant ? " M" : NULL); case N_SAND: return DISPLAY(C(0xded485), C(0xe3a112), n->variant ? " ~" : NULL); case N_NEEDLE_TREE: return DISPLAY_BG(C(0x015100)); case N_PATH: return DISPLAY_BG(C(0xa09700)); @@ -141,49 +122,87 @@ node_display gfx_render_node(client *c, vec2 pos, node *n) } } +char *gfx_render_entity(client *c, entity *e) +{ + switch (e->type) { + case ENTITY_PLAYER: + return e->id == c->self_id ? "🙂" : "😀"; + case ENTITY_FLOWER: switch (e->flower) { + case FLOWER_ROSE: return "🌹"; + case FLOWER_HIBISCUS: return "🌺"; + case FLOWER_SUNFLOWER: return "🌻"; + case FLOWER_DANDELION: return "🌼"; + case FLOWER_TULIP: return "🌷"; + default: return NULL; + } + default: return NULL; + } +} + #undef C #undef DISPLAY #undef DISPLAY_ERR void gfx_render(client *c, uint64_t dtime) { - rewind(c->termbuf); + rewind(c->frame); gfx_clear(c); - fprintf(c->termbuf, "\e[H"); - fprintf(c->termbuf, "%.*s\n", PSTR(c->server_motd)); - for (size_t i = 0; i < c->players.len; i++) - fprintf(c->termbuf, "%.*s\n", PSTR(c->players.data[i].name)); color_layer fg = {0}; color_layer bg = {0}; bg.bg = true; - for (size_t y = 0; y < c->map.bounds.size.y; y++) { - for (size_t x = 0; x < c->map.bounds.size.y; x++) { - uvec2 off = UVEC2(x, y); - vec2 pos = vec2_add(c->map.bounds.pos, CVEC2(off)); + // HUD + fprintf(c->frame, "\e[H"); + fprintf(c->frame, "%.*s\n", PSTR(c->server_motd)); + for (size_t i = 0; i < c->players.len; i++) + fprintf(c->frame, "%.*s\n", PSTR(c->players.data[i].name)); + + // map + tile_display tiles[c->map.bounds.size.x][c->map.bounds.size.y]; + for (size_t x = 0; x < c->map.bounds.size.x; x++) + for (size_t y = 0; y < c->map.bounds.size.x; y++) { + uvec2 off = UVEC2(x, y); + vec2 pos = vec2_add(c->map.bounds.pos, CVEC2(off)); + tiles[x][y] = gfx_render_node(c, pos, chunk_index(c->map, off)); + } - node_display dis = gfx_render_node(c, pos, chunk_index(c->map, off)); + // entities + for (size_t i = 0; i < c->entities.len; i++) { + entity *e = &c->entities.data[i]; + if (!box2_contains(c->map.bounds, e->pos)) // just to be safe... + continue; + vec2 pos = vec2_sub(e->pos, c->map.bounds.pos); + tile_display *t = &tiles[pos.x][pos.y]; + + char *tex = gfx_render_entity(c, e); + if (tex == NULL) + t->texture = "❓"; + else + t->texture = tex; + } + + // flush map & entities + for (size_t y = 0; y < c->map.bounds.size.y; y++) { + for (size_t x = 0; x < c->map.bounds.size.x; x++) { + tile_display dis = tiles[x][y]; gfx_set_color(c, &bg, dis.bg); - if (vec2_eq(pos, c->player_pos)) { - fprintf(c->termbuf, "🙂"); - } else if (dis.texture) { + if (dis.texture) { gfx_set_color(c, &fg, dis.fg); - fprintf(c->termbuf, "%s", dis.texture); + fprintf(c->frame, "%s", dis.texture); } else { - fprintf(c->termbuf, " "); + fprintf(c->frame, " "); } } gfx_clear_effects(c); - bg.has_old = false; fg.has_old = false; - fprintf(c->termbuf, "\n"); + bg.has_old = false; + fprintf(c->frame, "\n"); } - fflush(c->termbuf); - fprintf(stderr, "size: %ld\n", ftell(c->termbuf)); - write(STDOUT_FILENO, c->termbuf_buf, ftell(c->termbuf)); - //fwrite(c->termbuf_buf, 1, ftell(c->termbuf), stdout); - //fflush(stdout); + + // submit + fflush(c->frame); + write(STDOUT_FILENO, c->frame_buf, ftell(c->frame)); } void free_players(client *c) @@ -204,12 +223,13 @@ void client_exit(client *c, int ret) free(c->server_motd.data); free_players(c); + free(c->entities.data); peer_free(&c->conn); tcsetattr(STDIN_FILENO, TCSANOW, &c->oldtio); - if (c->termbuf) fclose(c->termbuf); - free(c->termbuf_buf); + if (c->frame) fclose(c->frame); + free(c->frame_buf); gfx_alt_buffer(false); exit(ret); @@ -250,6 +270,16 @@ void map_set_center(client *c, vec2 center) chunk_clear(c->map); chunk_copy(c->map, old); + + for (size_t i = 0; i < c->entities.len;) { + entity *e = &c->entities.data[i]; + if (box2_contains(c->map.bounds, e->pos)) { + i++; + continue; + } + + ARR_REMOVE(c->entities, e); + } } bool handle_nodes(str *pkt, client *c) @@ -276,6 +306,7 @@ bool handle_reset_map(str *pkt, client *c) { (void) pkt; chunk_clear(c->map); + c->entities.len = 0; return true; } @@ -310,29 +341,50 @@ bool handle_hi(str *pkt, client *c) 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++) { + while (pkt->len > 0) { entity_id id; entity_cmd cmd; if (!deser_entity_id(pkt, &id)) return false; if (!deser_entity_cmd(pkt, &cmd)) return false; + entity *e = entity_from_id(c, id); switch (cmd) { case ENTITY_ADD: { - entity_type type; - if (!deser_entity_type(pkt, &type)) return false; + entity ent = {0}; + ent.id = id; + if (!deser_entity_type(pkt, &ent.type)) return false; + if (!deser_vec2(pkt, &ent.pos)) return false; + switch (ent.type) { + case ENTITY_PLAYER: break; + case ENTITY_FLOWER: + if (!deser_flower_type(pkt, &ent.flower)) return false; + break; + default: return false; + } + if (e == NULL) + *ARR_APPEND(c->entities) = ent; + else + *e = ent; // this... shouldn't happen } break; - case ENTITY_REMOVE: break; - case ENTITY_MOVE: + case ENTITY_REMOVE: + if (e == NULL) continue; // consider returning false? shouldn't happen + ARR_REMOVE(c->entities, e); + 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); + if (e == NULL) continue; - break; + e->pos = pos; + + // TODO: remove entity if out of bounds? + // shouldn't happen tho, unless for moving the self player entity... + + if (id == c->self_id) + map_set_center(c, pos); + } break; default: return false; } } @@ -388,7 +440,7 @@ int main(int argc, char **argv) str server_name = S("server"); peer_init(&c.conn, socket, &server_name); - c.termbuf = open_memstream(&c.termbuf_buf, &c.termbuf_size); + c.frame = open_memstream(&c.frame_buf, &c.frame_size); gfx_alt_buffer(true); SEND_PKT(c.conn, SPKT_HI, diff --git a/src/server.c b/src/server.c index 974e396..108ae0d 100644 --- a/src/server.c +++ b/src/server.c @@ -44,8 +44,7 @@ typedef struct { bool auth; str name; entity_id id; - vec2 pos; - int8_t z; + int8_t z; // TODO: move to entity peer conn; } player; @@ -55,9 +54,109 @@ typedef struct { str passphrase; array(map) maps; arraybuf(player) players; + arraybuf(entity) entities; entity_id next_entity; } game; +player *player_from_id(game *g, entity_id id) +{ + for (size_t i = 0; i < g->players.len; i++) + if (g->players.data[i].id == id) + return &g->players.data[i]; + return NULL; +} + +entity *entity_from_id(game *g, entity_id id) +{ + for (size_t i = 0; i < g->entities.len; i++) + if (g->entities.data[i].id == id) + return &g->entities.data[i]; + return NULL; +} + +box2 player_sight(vec2 center) +{ + return box2_around(center, SIGHT_RANGE); +} + +void ser_entity_update(strbuf *w, entity *e, entity_cmd cmd) +{ + ser_entity_id(w, e->id); + ser_entity_cmd(w, cmd); + + switch (cmd) { + case ENTITY_ADD: + ser_entity_type(w, e->type); + ser_vec2(w, e->pos); + switch (e->type) { + case ENTITY_FLOWER: + ser_flower_type(w, e->flower); + break; + default: break; + } + break; + case ENTITY_MOVE: + ser_vec2(w, e->pos); + break; + case ENTITY_REMOVE: + break; + case ENTITY_CMD_COUNT: + break; // unreachable + } +} + +static vec2 *moved_get_pos(entity *e, entity *moved, vec2 *p_moved) +{ + return (e == moved) ? p_moved : &e->pos; +} + +static bool moved_can_see(entity *a, entity *b, entity *moved, vec2 *p_moved) +{ + vec2 *p_a = moved_get_pos(a, moved, p_moved); + vec2 *p_b = moved_get_pos(b, moved, p_moved); + return p_a && p_b && box2_contains(player_sight(*p_a), *p_b); +} + +static entity_cmd moved_check_update(entity *observer, entity *target, entity *moved, vec2 *before, vec2 *after) +{ + bool see_before = moved_can_see(observer, target, moved, before); + bool see_after = moved_can_see(observer, target, moved, after); + + if (see_before && !see_after) + return ENTITY_REMOVE; + else if (!see_before && see_after) + return ENTITY_ADD; + else if (see_before && see_after && target == moved) + return ENTITY_MOVE; + else + return ENTITY_CMD_COUNT; +} + +void moved_entity(game *g, entity *moved, vec2 *before, vec2 *after) +{ + for (size_t i = 0; i < g->players.len; i++) { + player *p = &g->players.data[i]; + if (!p->auth) continue; + + entity *observer = entity_from_id(g, p->id); + + if (observer == moved) { + SEND_PKT(p->conn, CPKT_ENTITY, + for (size_t j = 0; j < g->entities.len; j++) { + entity *target = &g->entities.data[j]; + entity_cmd cmd = moved_check_update(observer, target, moved, before, after); + if (cmd != ENTITY_CMD_COUNT) + ser_entity_update(&pkt, target, cmd); + } + ) + } else { + entity_cmd cmd = moved_check_update(observer, moved, moved, before, after); + if (cmd != ENTITY_CMD_COUNT) + SEND_PKT(p->conn, CPKT_ENTITY, ser_entity_update(&pkt, moved, cmd);) + } + } +} + node *map_node(game *g, vec2 p, int8_t z) { for (size_t i = 0; i < g->maps.len; i++) { @@ -84,10 +183,24 @@ bool silly_noise(vec2 pos, uint32_t grid, double sillyness, int seed) y += round(noise2d(y/grid, x/grid, 0, seed) * sillyness); return x % grid == 0 && y % grid == 0; -#undef SMOD } -void map_load_node(map *m, uvec2 v, color col) +entity *spawn_entity(game *g, entity_type type, vec2 pos) +{ + entity *e = ARR_APPEND(g->entities); + e->id = g->next_entity++; + e->type = type; + e->pos = pos; + return e; +} + +void remove_entity(game *g, entity *e) +{ + moved_entity(g, e, &e->pos, NULL); + ARR_REMOVE(g->entities, e); +} + +void map_load_node(game *g, map *m, uvec2 v, color col) { uint32_t ucol = color_to_u32(col); @@ -101,20 +214,21 @@ void map_load_node(map *m, uvec2 v, color col) return; switch (ucol) { + case 0xff9de2: + spawn_entity(g, ENTITY_FLOWER, pos)->flower = rand() % FLOWER_COUNT; + [[fallthrough]]; case 0x00880d: n->type = N_GRASS; n->variant = silly_noise(pos, 3, 1.0, 0); break; case 0x595959: n->type = N_ROCK; n->z = 1; break; + case 0x001b51: + spawn_entity(g, ENTITY_FLOWER, pos)->flower = FLOWER_DANDELION; + [[fallthrough]]; case 0x484848: n->type = N_ROCK; n->z = 2; break; + case 0x00ffe8: + spawn_entity(g, ENTITY_FLOWER, pos)->flower = FLOWER_DANDELION; + [[fallthrough]]; case 0x373737: n->type = N_ROCK; n->z = 3; break; - case 0x00ffe8: n->type = N_MOUNTAIN_FLOWER; - n->variant = FLOWER_DANDELION; - n->z = 3; - break; - case 0x001b51: n->type = N_MOUNTAIN_FLOWER; - n->variant = FLOWER_DANDELION; - n->z = 2; - break; case 0xa09700: n->type = N_PATH; break; case 0x015100: n->type = N_NEEDLE_TREE; break; case 0x00c6ff: n->type = N_WATER; @@ -122,9 +236,6 @@ void map_load_node(map *m, uvec2 v, color col) n->z = -1; break; case 0x5c3b12: n->type = N_PLANK; break; - case 0xff9de2: n->type = N_VALLEY_FLOWER; - n->variant = rand() % FLOWER_COUNT; - break; case 0x016300: n->type = N_BIG_TREE; break; case 0xfce84e: n->type = N_SAND; n->variant = silly_noise(pos, 4, 1.0, 1); @@ -137,7 +248,7 @@ void map_load_node(map *m, uvec2 v, color col) } } -bool map_load(map *m) +bool map_load(game *g, map *m) { #define TRY(expr, ...) if (!(expr)) { \ fprintf(stderr, __VA_ARGS__); \ @@ -188,7 +299,7 @@ bool map_load(map *m) } }*/ - map_load_node(m, UVEC2(x, y), (color) { p[0], p[1], p[2] }); + map_load_node(g, m, UVEC2(x, y), (color) { p[0], p[1], p[2] }); } } @@ -225,7 +336,7 @@ void send_nodes(player *p, game *g, box2 bounds, int8_t z) ) } -void player_free(player *p) +void player_cleanup(player *p) { peer_free(&p->conn); if (p->auth) @@ -234,11 +345,17 @@ void player_free(player *p) void player_remove(player *p, game *g) { - player_free(p); + player_cleanup(p); bool auth = p->auth; + entity_id id = p->id; + ARR_REMOVE(g->players, p); - if (auth) send_players(g); + + if (auth) { + remove_entity(g, entity_from_id(g, id)); + send_players(g); + } } void game_exit(game *g, int ret) @@ -253,7 +370,7 @@ void game_exit(game *g, int ret) free(g->maps.data); for (size_t i = 0; i < g->players.len; i++) - player_free(&g->players.data[i]); + player_cleanup(&g->players.data[i]); free(g->players.data); exit(ret); @@ -283,18 +400,23 @@ bool handle_hi(str pkt, player *p, game *g) p->auth = true; p->name = str_clone(name); - p->pos.x = p->pos.y = 0; + + entity *e = ARR_APPEND(g->entities); + e->id = p->id; + e->type = ENTITY_PLAYER; + e->pos.x = e->pos.y = 0; p->z = 0; SEND_PKT(p->conn, CPKT_HI, ser_str(&pkt, g->motd);) send_players(g); - send_nodes(p, g, box2_around(p->pos, SIGHT_RANGE), p->z); + moved_entity(g, e, NULL, &e->pos); + send_nodes(p, g, player_sight(e->pos), p->z); return true; } -bool handle_move(str pkt, player *p, [[maybe_unused]] game *g) +bool handle_move(str pkt, player *p, game *g) { dir d; if (!deser_dir(&pkt, &d)) return false; @@ -303,17 +425,16 @@ bool handle_move(str pkt, player *p, [[maybe_unused]] game *g) if (vec2_iszero(v)) return false; + entity *e = entity_from_id(g, p->id); + // 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); - ) + vec2 old_pos = e->pos; + e->pos = vec2_add(v, e->pos); + + moved_entity(g, e, &old_pos, &e->pos); // TODO: only send newly visible nodes - send_nodes(p, g, box2_around(p->pos, SIGHT_RANGE), p->z); + send_nodes(p, g, player_sight(e->pos), p->z); return true; } @@ -355,7 +476,7 @@ int main() .chunk = chunk_alloc((box2) { { -50, -50 }, { 100, 100 } }), }; - if (!map_load(&g.maps.data[0])) + if (!map_load(&g, &g.maps.data[0])) game_exit(&g, EXIT_FAILURE); if ((g.accept_fd = socket_create("0.0.0.0", "4560", true)) < 0) -- cgit v1.2.3