#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; arraybuf(entity) entities; str server_motd; chunk map; node *map_swap; char *frame_buf; size_t frame_size; FILE *frame; } client; player *player_from_id(client *c, entity_id id) { for (size_t i = 0; i < c->players.len; i++) if (c->players.data[i].id == id) return &c->players.data[i]; return NULL; } 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) { str out = enable ? S("\e[?1049h\e[?25l") : S("\e[?1049l\e[?25h"); write(STDOUT_FILENO, out.data, out.len); } typedef struct { bool bg; bool has_old; 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->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->frame, "\e[2J"); } #define C color_from_u32 #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; } tile_display; bool gfx_water_wave(vec2 pos) { return true; } 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_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(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)); case N_PLANK: return DISPLAY_BG(C(0x5c3b12)); case N_BIG_TREE: return DISPLAY_BG(C(0x016300)); default: return DISPLAY_ERR; } } 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->frame); gfx_clear(c); color_layer fg = {0}; color_layer bg = {0}; bg.bg = true; // 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)); } // 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 (dis.texture) { gfx_set_color(c, &fg, dis.fg); fprintf(c->frame, "%s", dis.texture); } else { fprintf(c->frame, " "); } } gfx_clear_effects(c); fg.has_old = false; bg.has_old = false; fprintf(c->frame, "\n"); } // submit fflush(c->frame); write(STDOUT_FILENO, c->frame_buf, ftell(c->frame)); } 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); free(c->entities.data); peer_free(&c->conn); tcsetattr(STDIN_FILENO, TCSANOW, &c->oldtio); if (c->frame) fclose(c->frame); free(c->frame_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); 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) { 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); c->entities.len = 0; 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) { 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 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: 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 (e == NULL) continue; 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; } } 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.frame = open_memstream(&c.frame_buf, &c.frame_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); } }