#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" typedef struct { str name; uint64_t id; } player; typedef struct { peer conn; struct termios oldtio; str name; str pass; uint64_t self_id; array(player) players; } client; void gfx_alt_buffer(bool enable) { if (enable) printf("\e[?1049h\e[?25l"); else printf("\e[?1049l\e[?25h"); } void gfx_clear() { printf("\e[2J"); } void gfx_render(client *c, uint64_t dtime) { gfx_clear(); printf("\e[H"); for (size_t i = 0; i < c->players.len; i++) printf("%.*s\n", PSTR(c->players.data[i].name)); 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_players(c); peer_free(&c->conn); tcsetattr(STDIN_FILENO, TCSANOW, &c->oldtio); gfx_alt_buffer(false); exit(ret); } void handle_input(client *c, char ch) { ch = tolower(ch); switch (ch) { case 'q': client_exit(c, EXIT_SUCCESS); break; default: break; } } bool handle_players(str *w, client *c) { free_players(c); uint16_t len; if (!deser_u16(w, &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 (str_cmp(p->name, c->name) == 0) c->self_id = p->id; p->name = str_clone(p->name); } return true; } bool handle_pkt(client *c, str pkt) { pkt_type type; if (!deser_pkt_type(&pkt, &type)) return false; switch (type) { case CPKT_PLAYERS: return handle_players(&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); gfx_alt_buffer(true); SEND_PKT(c.conn, SPKT_HI, ser_str(&pkt, c.name); ser_str(&pkt, c.pass); ) 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); } }