#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; } client; void gfx_alt_buffer(bool enable) { if (enable) printf("\e[?1049h\e[?25l"); else 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"); } void gfx_render_node(client *c, vec2 pos, node *n) { if (!n->present) { gfx_set_color(color_from_u32(0x555555), true); gfx_set_color(color_from_u32(0xffffff), false); printf(" ?"); return; } gfx_set_color(n->col, true); if (vec2_eq(pos, c->player_pos)) { printf("🥺"); return; } // TODO: persistence switch (n->type) { case N_GRASS: if (rand() % 10 == 0) { gfx_set_color(color_from_u32(0x1c571e), false); printf(" M"); return; } break; case N_WATER: if (rand() % 10 == 0) { gfx_set_color(color_from_u32(0x0d79de), false); printf(" ~"); return; } break; case N_SAND: if (rand() % 15 == 0) { gfx_set_color(color_from_u32(0xe3a112), false); printf(" ~"); return; } break; default: break; } printf(" "); } void gfx_render(client *c, uint64_t dtime) { gfx_clear(); printf("\e[H"); 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++) { uvec2 off = UVEC2(x, y); vec2 p = vec2_add(c->map.bounds.pos, CVEC2(off)); gfx_render_node(c, p, chunk_index(c->map, off)); } gfx_clear_effects(); printf("\n"); } 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); gfx_alt_buffer(false); fflush(stdout); 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); setvbuf(stdout, NULL, _IOFBF, 0); 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/60); 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); } }