#include #include #include #include #include #include #include #include #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; entity_id id; } player; typedef struct { peer conn; struct termios oldtio; str name; str pass; entity_id self_id; array(player) players; str server_motd; chunk map; node *map_swap; vec2 player_pos; char *termbuf_buf; size_t termbuf_size; FILE *termbuf; } client; void gfx_alt_buffer(bool enable) { if (enable) printf("\e[?1049h\e[?25l"); else printf("\e[?1049l\e[?25h"); fflush(stdout); } void gfx_clear_effects(client *c) { fprintf(c->termbuf, "\e[0m"); } void gfx_set_color(client *c, color col, bool bg, bool *has_old, color *old) { if (*has_old && old->r == col.r && old->g == col.g && old->b == col.b) return; fprintf(c->termbuf, "\e[%u;2;%u;%u;%um", bg ? 48 : 38, col.r, col.g, col.b); *has_old = true; *old = col; } void gfx_clear(client *c) { fprintf(c->termbuf, "\e[2J"); } #define C color_from_u32 #define DISPLAY_BG(X) (node_display) { X, { 0 }, NULL } #define DISPLAY(...) (node_display) { __VA_ARGS__ } #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); } bool gfx_water_wave(vec2 pos) { return true; } node_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_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_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)); case N_PLANK: return DISPLAY_BG(C(0x5c3b12)); case N_BIG_TREE: return DISPLAY_BG(C(0x016300)); default: return DISPLAY_ERR; } } #undef C #undef DISPLAY #undef DISPLAY_ERR void gfx_render(client *c, uint64_t dtime) { rewind(c->termbuf); 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)); bool has_bg = false; bool has_fg = false; color bg; color fg; 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)); node_display dis = gfx_render_node(c, pos, chunk_index(c->map, off)); gfx_set_color(c, dis.bg, true, &has_bg, &bg); if (vec2_eq(pos, c->player_pos)) { fprintf(c->termbuf, "🙂"); } else if (dis.texture) { gfx_set_color(c, dis.fg, false, &has_fg, &fg); fprintf(c->termbuf, "%s", dis.texture); } else { fprintf(c->termbuf, " "); } } gfx_clear_effects(c); has_bg = false; has_fg = false; fprintf(c->termbuf, "\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); } void free_players(client *c) { for (size_t i = 0; i < c->players.len; i++) free(c->players.data[i].name.data); free(c->players.data); c->players.len = 0; c->players.data = NULL; } void client_exit(client *c, int ret) { free(c->name.data); free(c->pass.data); free(c->server_motd.data); free_players(c); peer_free(&c->conn); tcsetattr(STDIN_FILENO, TCSANOW, &c->oldtio); if (c->termbuf) fclose(c->termbuf); free(c->termbuf_buf); gfx_alt_buffer(false); exit(ret); } dir dir_from_key(char ch) { switch (ch) { case 'w': return DIR_UP; case 'a': return DIR_LEFT; case 's': return DIR_DOWN; case 'd': return DIR_RIGHT; default: return 4; } } void handle_input(client *c, char ch) { ch = tolower(ch); dir move; if ((move = dir_from_key(ch)) != 4) SEND_PKT(c->conn, SPKT_MOVE, ser_dir(&pkt, move);) } void map_set_center(client *c, vec2 center) { vec2 pos = vec2_sub(center, CVEC2(uvec2_div(c->map.bounds.size, 2))); if (vec2_eq(c->map.bounds.pos, pos)) return; chunk old = c->map; c->map.data = c->map_swap; c->map.bounds.pos = pos; c->map_swap = old.data; chunk_clear(c->map); chunk_copy(c->map, old); } 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 > PKT_NODES_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(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(pkt, &p->name)) return false; if (!deser_entity_id(pkt, &p->id)) return false; if (str_eq(p->name, c->name)) c->self_id = p->id; p->name = str_clone(p->name); } return true; } bool handle_hi(str *pkt, client *c) { str motd; if (!deser_str(pkt, &motd)) return false; c->server_motd = str_clone(motd); return true; } 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++) { entity_id id; entity_cmd cmd; if (!deser_entity_id(pkt, &id)) return false; if (!deser_entity_cmd(pkt, &cmd)) return false; switch (cmd) { case ENTITY_ADD: { entity_type type; if (!deser_entity_type(pkt, &type)) return false; } break; case ENTITY_REMOVE: 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); break; default: return false; } } return true; } bool handle_pkt(client *c, str pkt) { pkt_type type; if (!deser_pkt_type(&pkt, &type)) return false; 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); case CPKT_ENTITY: return handle_entity(&pkt, c); default: return false; } } int main(int argc, char **argv) { if (argc < 4) { fprintf(stderr, "usage: %s server port name [pass]\n", argv[0]); return EXIT_FAILURE; } signal_setup(); client c = {0}; const char *server = argv[1]; const char *port = argv[2]; c.name = str_clone(str_intro(argv[3])); c.pass = str_clone(argc < 5 ? S("") : str_intro(argv[4])); c.conn.socket = -1; tcgetattr(STDIN_FILENO, &c.oldtio); // TODO: handle error struct termios newtio = c.oldtio; newtio.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newtio); // TODO: handle error SET_NONBLOCK(STDIN_FILENO); // TODO: handle error int socket = socket_create(server, port, false); if (socket < 0) client_exit(&c, EXIT_FAILURE); str server_name = S("server"); peer_init(&c.conn, socket, &server_name); c.termbuf = open_memstream(&c.termbuf_buf, &c.termbuf_size); gfx_alt_buffer(true); SEND_PKT(c.conn, SPKT_HI, ser_str(&pkt, c.name); 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/30); for (;;) { struct pollfd fds[2]; fds[0].fd = STDIN_FILENO; fds[0].events = POLLIN; fds[1] = peer_prepare(&c.conn); if (poll(fds, 2, ticker_timeout(&t)) < 0) { switch (errno) { case EINTR: break; default: perror("poll"); continue; } } if (signal_stop) client_exit(&c, EXIT_SUCCESS); if (fds[0].revents) { // this is cursed // maybe dont do this idk char ch; errno = 0; while (read(STDIN_FILENO, &ch, 1) == 1) handle_input(&c, ch); switch (errno) { case 0: case EWOULDBLOCK: case EINTR: break; default: perror("read"); break; } } str pkt = peer_recv(&c.conn, fds[1]); if (c.conn.disco) client_exit(&c, EXIT_SUCCESS); if (pkt.len > 0 && !handle_pkt(&c, pkt)) invalid_pkt(&c.conn, pkt); uint64_t dtime; if (ticker_tick(&t, &dtime)) gfx_render(&c, dtime); } }