UI: Add pazz0's dungeon-crawler-like interface - sacc - sacc(omys), simple console gopher client
HTML git clone git://bitreich.org/sacc/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/sacc/
DIR Log
DIR Files
DIR Refs
DIR Tags
DIR LICENSE
---
DIR commit d16bb53db353da7b817b18809adf57b57c4f973e
DIR parent 0fce134e1baa2ae47aeb0ee7c091e6c25651bcea
HTML Author: Quentin Rameau <quinq@fifth.space>
Date: Thu, 28 Mar 2024 12:13:51 +0100
UI: Add pazz0's dungeon-crawler-like interface
This definitely looks fun and interesting
Diffstat:
A ui_rogue.c | 938 +++++++++++++++++++++++++++++++
1 file changed, 938 insertions(+), 0 deletions(-)
---
DIR diff --git a/ui_rogue.c b/ui_rogue.c
@@ -0,0 +1,938 @@
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <term.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "common.h"
+#include "config.h"
+
+#define C(c) #c
+#define S(c) C(c)
+
+/* ncurses doesn't define those in term.h, where they're used */
+#ifndef OK
+#define OK (0)
+#endif
+#ifndef ERR
+#define ERR (-1)
+#endif
+
+static struct termios tsave;
+static struct termios tsacc;
+static Item *curentry;
+static int termset = ERR;
+static char bufout[256];
+static char bufout2[256];
+
+void drawscreen(void);
+
+uint32_t
+fnv1a(int n,...)
+{
+ int i;
+ char *s;
+ va_list l;
+ uint32_t h;
+
+ h = 0x811c9dc5;
+
+ va_start(l, n);
+ for (i = 0; i < n; i++) {
+ for (s = va_arg(l, char*); *s; s++) {
+ h ^= *s;
+ h *= 0x01000193;
+ }
+ }
+ va_end(l);
+
+ return h;
+}
+
+uint32_t
+xorshift(uint32_t *s)
+{
+ *s ^= *s << 13;
+ *s ^= *s >> 17;
+ *s ^= *s << 5;
+ return *s;
+}
+
+struct cell {
+ char c;
+ size_t nitems;
+ Item **items;
+};
+
+#define MAPHEIGHT (25)
+#define MAPWIDTH (80)
+struct cell map[MAPHEIGHT][MAPWIDTH];
+
+struct room {
+ struct room *p;
+ void *d;
+ size_t x, y;
+ size_t w, h;
+};
+
+struct rect {
+ struct rect *next, *next2;
+ struct room *room;
+ size_t x1, y1;
+ size_t x2, y2;
+ size_t d;
+};
+
+struct rect *
+randomneighbor(struct rect *x, struct rect *rs, uint32_t *prng)
+{
+ struct rect *r, *result;
+ size_t n;
+
+ n = 0;
+ result = NULL;
+ for (r = rs; r; r = r->next) {
+ if (r->y2 < x->y1 || r->y1 > x->y2 || r->x2 < x->x1 || r->x1 > x->x2)
+ continue;
+ if ((r->y2 == x->y1 || r->y1 == x->y2) && (r->x2 == x->x1 || r->x1 == x->x2))
+ continue;
+ n++;
+ if (xorshift(prng) / (1. + UINT32_MAX) < 1. / n)
+ result = r;
+ }
+
+ return result;
+}
+
+#define ROOM_HEIGHT_MIN 3
+#define ROOM_WIDTH_MIN 5
+#define ROOM_MARGIN_MIN 1
+#define CELL_HEIGHT_MIN (ROOM_HEIGHT_MIN + ROOM_MARGIN_MIN + 3)
+#define CELL_WIDTH_MIN (ROOM_WIDTH_MIN + ROOM_MARGIN_MIN + 3)
+size_t
+generaterooms_gnarf(uint32_t prng, struct room *rs, size_t l)
+{
+ struct rect *queuehead, *queuetail;
+ struct rect *r, *t;
+ struct rect *rects, *walk;
+ size_t w, h, i, j, rl, n;
+ int vertical;
+ struct room *room;
+
+ r = malloc(sizeof(*r));
+ r->x1 = r->y1 = ROOM_MARGIN_MIN;
+ r->x2 = MAPWIDTH;
+ r->y2 = MAPHEIGHT;
+ r->d = 0;
+
+ queuetail = r;
+ queuetail->next = NULL;
+ queuehead = r;
+
+ rects = NULL;
+ rl = 0;
+
+ while (queuehead) {
+ r = queuehead;
+ if (queuetail == queuehead)
+ queuetail = NULL;
+ queuehead = queuehead->next;
+
+ if (r->x2 - r->x1 >= CELL_WIDTH_MIN * 2 && r->y2 - r->y1 >= CELL_HEIGHT_MIN * 2) {
+ vertical = xorshift(&prng) & 1;
+ } else if (r->x2 - r->x1 >= CELL_WIDTH_MIN * 2) {
+ vertical = 0;
+ } else if (r->y2 - r->y1 >= CELL_HEIGHT_MIN * 2) {
+ vertical = 1;
+ } else {
+ r->next = rects;
+ rects = r;
+ rl++;
+ continue;
+ }
+
+ if (vertical) {
+ w = r->x2 - r->x1;
+ h = CELL_HEIGHT_MIN + xorshift(&prng) % (1 + r->y2 - r->y1 - CELL_HEIGHT_MIN * 2);
+ } else {
+ w = CELL_WIDTH_MIN + xorshift(&prng) % (1 + r->x2 - r->x1 - CELL_WIDTH_MIN * 2);
+ h = r->y2 - r->y1;
+ }
+
+ t = malloc(sizeof(*t));
+ t->x1 = r->x1;
+ t->y1 = r->y1;
+ t->x2 = r->x1 + w;
+ t->y2 = r->y1 + h;
+ t->d = r->d + 1;
+ t->next = NULL;
+ t->room = NULL;
+
+ if (!queuetail) {
+ queuehead = t;
+ queuetail = t;
+ } else {
+ queuetail->next = t;
+ queuetail = t;
+ }
+
+ t = malloc(sizeof(*t));
+ if (vertical) {
+ t->x1 = r->x1;
+ t->y1 = r->y1 + h;
+ } else {
+ t->x1 = r->x1 + w;
+ t->y1 = r->y1;
+ }
+ t->x2 = r->x2;
+ t->y2 = r->y2;
+ t->d = r->d + 1;
+ t->next = NULL;
+ t->room = NULL;
+
+ queuetail->next = t;
+ queuetail = t;
+
+ free(r);
+ }
+
+ if (l > rl)
+ l = rl;
+
+ for (r = rects; r; r = r->next) {
+ if (MAPHEIGHT / 2 >= r->y1 && MAPHEIGHT / 2 < r->y2 &&
+ MAPWIDTH / 2 >= r->x1 && MAPWIDTH / 2 < r->x2)
+ break;
+ }
+
+ i = 0;
+ rs[i].w = ROOM_WIDTH_MIN + xorshift(&prng) % (1 + r->x2 - r->x1 - ROOM_MARGIN_MIN - ROOM_WIDTH_MIN);
+ rs[i].h = ROOM_HEIGHT_MIN + xorshift(&prng) % (1 + r->y2 - r->y1 - ROOM_MARGIN_MIN - ROOM_HEIGHT_MIN);
+ rs[i].x = r->x1 + xorshift(&prng) % (1 + r->x2 - r->x1 - ROOM_MARGIN_MIN - rs[i].w);
+ rs[i].y = r->y1 + xorshift(&prng) % (1 + r->y2 - r->y1 - ROOM_MARGIN_MIN - rs[i].h);
+ rs[i].p = NULL;
+ r->room = &rs[i];
+
+ walk = r;
+ walk->next2 = NULL;
+
+ i++;
+ for (; i < l;) {
+ t = randomneighbor(r, rects, &prng);
+ if (!t || t->room) {
+ n = 0;
+ for (t = walk; t; t = t->next2) {
+ n++;
+ if (xorshift(&prng) / (1. + UINT32_MAX) < 1. / n)
+ r = t;
+
+ }
+ continue;
+ }
+ rs[i].w = ROOM_WIDTH_MIN + xorshift(&prng) % (1 + t->x2 - t->x1 - ROOM_MARGIN_MIN - ROOM_WIDTH_MIN);
+ rs[i].h = ROOM_HEIGHT_MIN + xorshift(&prng) % (1 + t->y2 - t->y1 - ROOM_MARGIN_MIN - ROOM_HEIGHT_MIN);
+ rs[i].x = t->x1 + xorshift(&prng) % (1 + t->x2 - t->x1 - ROOM_MARGIN_MIN - rs[i].w);
+ rs[i].y = t->y1 + xorshift(&prng) % (1 + t->y2 - t->y1 - ROOM_MARGIN_MIN - rs[i].h);
+ rs[i].p = r->room;
+ t->room = &rs[i];
+ i++;
+ r = t;
+ r->next2 = walk;
+ walk = r;
+ }
+
+ for (r = rects; r;) {
+ t = r->next;
+ free(r);
+ r = t;
+ }
+
+ return l;
+}
+
+size_t
+distance(size_t x1, size_t y1, size_t x2, size_t y2)
+{
+ size_t d;
+
+ if (y1 < y2)
+ d = y2 - y1;
+ else
+ d = y1 - y2;
+ if (x1 < x2)
+ d += x2 - x1;
+ else
+ d += x1 - x2;
+
+ return d;
+}
+
+void
+nearestpoints(struct room *a, struct room *b, size_t *ax, size_t *ay, size_t *bx, size_t *by)
+{
+ if (a->y >= b->y && a->y < b->y + b->h) {
+ *ay = *by = a->y;
+ } else if (b->y >= a->y && b->y < a->y + a->h) {
+ *ay = *by = b->y;
+ } else if (a->y >= b->y) {
+ *ay = a->y;
+ *by = b->y + b->h - 1;
+ } else if (b->y >= a->y) {
+ *ay = a->y + a->h - 1;
+ *by = b->y;
+ }
+
+ if (a->x >= b->x && a->x < b->x + b->w) {
+ *ax = *bx = a->x;
+ } else if (b->x >= a->x && b->x < a->x + a->w) {
+ *ax = *bx = b->x;
+ } else if (a->x >= b->x) {
+ *ax = a->x;
+ *bx = b->x + b->w - 1;
+ } else if (b->x >= a->x) {
+ *ax = a->x + a->w - 1;
+ *bx = b->x;
+ }
+}
+
+void
+connectrooms(struct room *a, struct room *b)
+{
+ size_t i, j;
+ ssize_t ii;
+ size_t x1, y1;
+ size_t x2, y2;
+
+ nearestpoints(a, b, &x1, &y1, &x2, &y2);
+
+ if (y1 > y2) {
+ ii = -1;
+ } else if (y2 > y1) {
+ ii = 1;
+ } else {
+ ii = 0;
+ }
+
+/*
+printf("%lu\t%lu\t%d\n", y1, y2, ii);
+*/
+ for (i = y1; i != y2; i += ii)
+ map[i][x1].c = '.';
+
+ if (x1 > x2) {
+ ii = -1;
+ } else if (x2 > x1) {
+ ii = 1;
+ } else {
+ ii = 0;
+ }
+
+ for (i = x1; i != x2; i += ii)
+ map[y2][i].c = '.';
+}
+
+void
+rendermap(void)
+{
+ size_t i, j;
+
+ for (i = 0; i < MAPHEIGHT; i++) {
+ for (j = 0; j < MAPWIDTH; j++)
+ putchar(map[i][j].c);
+ putchar('\n');
+ }
+}
+
+size_t
+placeitems_hash(Item *item, size_t *assocs, size_t k)
+{
+ Dir *dir;
+ Item *citem;
+ size_t i;
+
+ dir = item->dat;
+ for (i = 0; i < dir->nitems; i++) {
+ citem = &dir->items[i];
+ /* TODO Somewhere else */
+ if (!citem->host || !citem->port || !citem->selector)
+ continue;
+ assocs[i] = fnv1a(6, item->host, item->port, item->selector, citem->host, citem->port, citem->selector) % k;
+ }
+
+ return k;
+}
+
+#define POSITIONS_LENGTH 4
+enum {
+ Portal,
+ StaircaseDown,
+ Bookshelf,
+ Back
+};
+
+size_t px, py;
+
+void
+generatemap(Item *item, int new)
+{
+ Dir *dir;
+ Item *citem;
+ size_t i, j, k, l, ir;
+ size_t x, y;
+ ssize_t n, m;
+ size_t *cassocs;
+ struct room *rooms, *r;
+ struct {
+ unsigned char x, y;
+ } positions[POSITIONS_LENGTH];
+ uint32_t prng;
+ char buffer[3];
+
+ for (i = 0; i < MAPHEIGHT; i++) {
+ for (j = 0; j < MAPWIDTH; j++) {
+ map[i][j].c = '#';
+ free(map[i][j].items);
+ map[i][j].items = NULL;
+ map[i][j].nitems = 0;
+ }
+ }
+
+ dir = item->dat;
+ for (j = l = 0; j < dir->nitems; j++) {
+ if (dir->items[j].type == '0' ||
+ dir->items[j].type == '1')
+ l++;
+ }
+
+ k = 1 + l / 10;
+ rooms = calloc(k, sizeof(*rooms));
+ if (!rooms)
+ return;
+ k = generaterooms_gnarf(fnv1a(3, item->host, item->port, item->selector), rooms, k);
+
+ cassocs = calloc(dir->nitems, sizeof(*cassocs));
+ if (!cassocs)
+ goto cleanup;
+
+ k = placeitems_hash(item, cassocs, k);
+
+ /* Insert rooms */
+ for (i = 0; i < k; i++) {
+ for (y = rooms[i].y; y < rooms[i].y + rooms[i].h; y++) {
+ for (x = rooms[i].x; x < rooms[i].x + rooms[i].w; x++)
+ map[y][x].c = '.';
+ }
+ }
+
+ /* Insert connections */
+ for (i = 0; i < k; i++) {
+ if (rooms[i].p)
+ connectrooms(&rooms[i], rooms[i].p);
+ }
+
+ /*
+ Insert items
+ The placement of items affects the initial placement of the player, because they could have gone back to this map, so they should appear at the elevator/portal/stair they used.
+ */
+ ir = fnv1a(4, item->host, item->port, item->selector, "initial_room") % k;
+
+ for (i = 0; i < k; i++) {
+ snprintf(buffer, sizeof(buffer), "%d", i);
+ prng = fnv1a(4, item->host, item->port, item->selector, buffer);
+ for (j = 0, n = 0, m = rooms[i].h * rooms[i].w; j < m; j++) {
+ if ((m - j) * (xorshift(&prng) / (double)UINT32_MAX) < POSITIONS_LENGTH - n) {
+ positions[n].x = rooms[i].x + j % rooms[i].w;
+ positions[n].y = rooms[i].y + j / rooms[i].w;
+ n++;
+ }
+ if (n == POSITIONS_LENGTH)
+ break;
+ }
+ for (j = 0; j < dir->nitems; j++) {
+ if (cassocs[j] != i)
+ continue;
+
+ citem = &dir->items[j];
+ switch (citem->type) {
+ case '0':
+ x = positions[Bookshelf].x;
+ y = positions[Bookshelf].y;
+ if (map[y][x].nitems)
+ map[y][x].c = 'E';
+ else
+ map[y][x].c = '?';
+ break;
+ case '1':
+ if (strcmp(citem->host, item->host) || strcmp(citem->port, item->port)) {
+ x = positions[Portal].x;
+ y = positions[Portal].y;
+ if (map[y][x].nitems)
+ map[y][x].c = 'O';
+ else
+ map[y][x].c = '0';
+ } else {
+ x = positions[StaircaseDown].x;
+ y = positions[StaircaseDown].y;
+ if (map[y][x].nitems)
+ map[y][x].c = 'L';
+ else
+ map[y][x].c = '>';
+ }
+ break;
+ default:
+ continue;
+ }
+ map[y][x].nitems++;
+ map[y][x].items = realloc(map[y][x].items, map[y][x].nitems * sizeof(*map[y][x].items));
+ map[y][x].items[map[y][x].nitems-1] = citem;
+
+ if (new && j == dir->curline && citem->raw) {
+ px = x;
+ py = y;
+ }
+ }
+
+ if (i == ir && item->entry != item) {
+ y = positions[Back].y;
+ x = positions[Back].x;
+ if (strcmp(item->entry->host, item->host) || strcmp(item->entry->port, item->port))
+ map[y][x].c = '0';
+ else
+ map[y][x].c = '<';
+ map[y][x].nitems++;
+ map[y][x].items = realloc(map[y][x].items, map[y][x].nitems * sizeof(*map[y][x].items));
+ map[y][x].items[map[y][x].nitems-1] = item->entry;
+ }
+
+ if (i == ir && new && !dir->items[dir->curline].raw) {
+ px = positions[Back].x;
+ py = positions[Back].y;
+ }
+ }
+ free(cassocs);
+
+cleanup:
+ free(rooms);
+}
+
+void
+uisetup(void)
+{
+ tcgetattr(0, &tsave);
+ tsacc = tsave;
+ tsacc.c_lflag &= ~(ECHO|ICANON);
+ tsacc.c_cc[VMIN] = 1;
+ tsacc.c_cc[VTIME] = 0;
+ tcsetattr(0, TCSANOW, &tsacc);
+
+ if (termset != OK)
+ /* setupterm call exits on error */
+ termset = setupterm(NULL, 1, NULL);
+ putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ fflush(stdout);
+}
+
+void
+uicleanup(void)
+{
+ tcsetattr(0, TCSANOW, &tsave);
+
+ if (termset != OK)
+ return;
+
+ putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ fflush(stdout);
+}
+
+char *
+uiprompt(char *fmt, ...)
+{
+ va_list ap;
+ char *input = NULL;
+ size_t n;
+ ssize_t r;
+
+ putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
+ putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ va_start(ap, fmt);
+ vsnprintf(bufout, sizeof(bufout), fmt, ap);
+ va_end(ap);
+
+ n = mbsprint(bufout, columns);
+
+ putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ putp(tparm(cursor_address, lines-1, n, 0, 0, 0, 0, 0, 0, 0));
+
+ tsacc.c_lflag |= (ECHO|ICANON);
+ tcsetattr(0, TCSANOW, &tsacc);
+ fflush(stdout);
+
+ n = 0;
+ r = getline(&input, &n, stdin);
+
+ tsacc.c_lflag &= ~(ECHO|ICANON);
+ tcsetattr(0, TCSANOW, &tsacc);
+ putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ fflush(stdout);
+
+ if (r == -1) {
+ clearerr(stdin);
+ clear(&input);
+ } else if (input[r - 1] == '\n') {
+ input[--r] = '\0';
+ }
+
+ return input;
+}
+
+void
+displaybar(char *s) {
+ size_t n;
+
+ putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ putp(tparm(cursor_address, lines-2, 0, 0, 0, 0, 0, 0, 0, 0));
+ putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ n = mbsprint(s, columns);
+ for (n = columns - n; n; n--)
+ putchar(' ');
+
+ putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ fflush(stdout);
+}
+
+void
+vdisplayinfoline(char *fmt, va_list ap)
+{
+ putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ vsnprintf(bufout, sizeof(bufout), fmt, ap);
+
+ mbsprint(bufout, columns);
+
+ putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ fflush(stdout);
+}
+
+void
+uistatus(char *fmt, ...)
+{
+ va_list ap;
+ size_t n;
+
+ putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ va_start(ap, fmt);
+ n = vsnprintf(bufout, sizeof(bufout), fmt, ap);
+ va_end(ap);
+
+ if (n < sizeof(bufout)-1) {
+ snprintf(bufout+n, sizeof(bufout)-n,
+ " [Press a key to continue \xe2\x98\x83]");
+ }
+
+ mbsprint(bufout, columns);
+
+ putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ fflush(stdout);
+
+ getchar();
+}
+
+void
+displayinfoline(char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vdisplayinfoline(fmt, ap);
+ va_end(ap);
+}
+
+Item *
+showmenu(char *title, Item **item, size_t l)
+{
+ size_t i;
+
+ putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ printf("%s\n", title);
+ putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ for (i = 0; i < l; i++)
+ printf("%lu\t%s\n", i, item[i]->username);
+
+ if (!scanf("%lu", &i) || i >= l)
+ return NULL;
+ fflush(stdout);
+
+ return item[i];
+}
+
+Item *
+prompt(char *text, Item *item)
+{
+ displayinfoline(text, item->username);
+ getchar();
+ return item;
+}
+
+Item *
+interact(Item *item)
+{
+ Item *selection;
+
+ selection = NULL;
+
+ switch (map[py][px].c) {
+ case '?':
+ case '0':
+ case '>':
+ case '<':
+ selection = map[py][px].items[0];
+ break;
+ case 'E':
+ selection = showmenu("Bookshelf", map[py][px].items, map[py][px].nitems);
+ break;
+ case 'O':
+ selection = showmenu("Portal machine", map[py][px].items, map[py][px].nitems);
+ break;
+ case 'L':
+ selection = showmenu("Elevator", map[py][px].items, map[py][px].nitems);
+ break;
+ }
+
+ drawscreen();
+
+ if (selection) {
+ switch (map[py][px].c) {
+ case '?':
+ case 'E':
+ displayinfoline("A loading bar?! In a book?!");
+ break;
+ case 'O':
+ case '0':
+ displayinfoline("You are getting transported through time and space.");
+ break;
+ case 'L':
+ displayinfoline("You hear elevator music...");
+ break;
+ case '<':
+ case '>':
+ displayinfoline("Too many stairs...");
+ break;
+ }
+ }
+
+ return selection;
+}
+
+void
+describe(size_t x, size_t y, int verbose)
+{
+ switch (map[y][x].c) {
+ case 'E':
+ displayinfoline("A bookshelf.");
+ break;
+ case 'O':
+ displayinfoline("A portal machine.");
+ break;
+ case 'L':
+ displayinfoline("An elevator.");
+ break;
+ case '?':
+ case '>':
+ case '<':
+ case '0':
+ if (*map[y][x].items[0]->username) {
+ displayinfoline("'%s'.", map[y][x].items[0]->username);
+ } else {
+ itemuri(map[y][x].items[0], bufout2, sizeof(bufout2));
+ displayinfoline("'%s'.", bufout2);
+ }
+ break;
+ default:
+ if (verbose) {
+ switch (map[y][x].c) {
+ case '.':
+ displayinfoline("Floor.");
+ break;
+ case '#':
+ displayinfoline("Wall.");
+ break;
+ }
+ } else {
+ displayinfoline("");
+ }
+ break;
+ }
+}
+
+void
+move(ssize_t dx, ssize_t dy)
+{
+ size_t x, y;
+
+ /* allow wraparound of the world for the lulz, even if it's not happening */
+ y = (MAPHEIGHT + py + dy) % MAPHEIGHT;
+ x = (MAPWIDTH + px + dx) % MAPWIDTH;
+
+ if (map[y][x].c == '#')
+ return;
+
+ putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+ putchar(map[py][px].c);
+
+ py = y;
+ px = x;
+ putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+ putchar('@');
+ putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+
+ describe(x, y, 0);
+ putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0 ));
+}
+
+void
+drawscreen(void)
+{
+ Dir *dir;
+
+ putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ rendermap();
+
+ if (curentry->entry != curentry && (dir = curentry->entry->dat)) {
+ displaybar(dir->items[dir->curline].username);
+ } else {
+ itemuri(curentry, bufout, sizeof(bufout));
+ displaybar(bufout);
+ }
+
+ move(0, 0);
+
+}
+
+void
+uidisplay(Item *entry)
+{
+ if (!entry || entry->type != '1')
+ return;
+
+ generatemap(entry, curentry != entry);
+
+ curentry = entry;
+ drawscreen();
+}
+
+void
+lookmode(void)
+{
+ size_t x, y;
+
+ x = px;
+ y = py;
+
+ for (;;) {
+ switch (getchar()) {
+ case 0x1B:
+ case 'q':
+ putp(tparm(cursor_address, py, px, 0, 0, 0, 0, 0, 0, 0));
+ return;
+ case 'h':
+ x = (MAPWIDTH + x - 1) % MAPWIDTH;
+ break;
+ case 'j':
+ y = (y + 1) % MAPHEIGHT;
+ break;
+ case 'k':
+ y = (MAPHEIGHT + y - 1) % MAPHEIGHT;
+ break;
+ case 'l':
+ x = (x + 1) % MAPWIDTH;
+ break;
+ }
+ putp(tparm(cursor_address, y, x, 0, 0, 0, 0, 0, 0, 0));
+ describe(x, y, 1);
+ }
+}
+
+Item *
+uiselectitem(Item *entry)
+{
+ Dir *dir;
+ Item *e;
+ size_t i;
+
+ if (!entry || !(dir = entry->dat))
+ return NULL;
+
+ for (;;) {
+ switch (getchar()) {
+ case 'h':
+ move(-1, 0);
+ break;
+ case 'j':
+ move(0, 1);
+ break;
+ case 'k':
+ move(0, -1);
+ break;
+ case 'l':
+ move(1, 0);
+ break;
+ case 'L':
+ lookmode();
+ break;
+ case ' ':
+ /* Portals, stairs, bookshelfs */
+ if (e = interact(entry)) {
+ if (e->type == '1') {
+ for (i = 0; i < dir->nitems; i++) {
+ if (e == &dir->items[i]) {
+ dir->curline = i;
+ break;
+ }
+ }
+ }
+ return e;
+ }
+ break;
+ case 'q':
+ return NULL;
+ }
+ }
+}
+
+void
+uisigwinch(int signal)
+{
+ Dir *dir;
+
+ if (termset == OK)
+ del_curterm(cur_term);
+ termset = setupterm(NULL, 1, NULL);
+
+ if (!curentry || !(dir = curentry->dat))
+ return;
+
+ uidisplay(curentry);
+}