ui_rogue.c (29707B)
1 #include <errno.h> 2 #include <signal.h> 3 #include <stdarg.h> 4 #include <stdint.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <term.h> 9 #include <termios.h> 10 #include <unistd.h> 11 #include <sys/select.h> 12 #include <sys/types.h> 13 14 #include "common.h" 15 #include "config.h" 16 17 #define C(c) #c 18 #define S(c) C(c) 19 20 /* ncurses doesn't define those in term.h, where they're used */ 21 #ifndef OK 22 #define OK (0) 23 #endif 24 #ifndef ERR 25 #define ERR (-1) 26 #endif 27 28 #define maplines (lines - 2) 29 30 enum { 31 Blocks = 1, 32 Standout = 2, 33 Important = 4 34 }; 35 36 struct cell; 37 struct tile { 38 char c; 39 char flags; 40 char *name; 41 char *description; 42 char *afterinteract; 43 Item *(*interact)(struct cell *); 44 }; 45 46 struct cell { 47 struct tile *tile; 48 size_t nitems; 49 Item **items; 50 }; 51 52 struct room { 53 size_t x, y; 54 size_t w, h; 55 }; 56 57 struct rect { 58 struct rect *next, *next2; 59 struct rect *p; 60 size_t x1, y1; 61 size_t x2, y2; 62 size_t d; 63 union { 64 void *p; 65 int i; 66 } data; 67 }; 68 69 static struct termios tsave; 70 static struct termios tsacc; 71 static Item *curentry; 72 static int termset = ERR; 73 static char bufout[256]; 74 static char bufout2[256]; 75 76 size_t ox, oy; 77 size_t px, py; 78 79 #define MAPHEIGHT (50) 80 #define MAPWIDTH (160) 81 struct cell map[MAPHEIGHT][MAPWIDTH]; 82 83 enum { 84 DungeonScreen, 85 MenuScreen 86 } screen; 87 88 Item *interactitem(struct cell *); 89 Item *interactmenu(struct cell *); 90 91 struct tile tile_void = { ' ', Blocks, "Void", "The void. The thing which is everywhere where nothing is.", NULL, NULL }; 92 struct tile tile_floor = { '.', 0, "Floor", "An ordinary stone floor.", NULL, NULL }; 93 struct tile tile_corridor = { '#', 0, "Different Floor", "This floor looks different than the other one.", NULL, NULL }; 94 struct tile tile_verticalwall = { '|', Blocks, "Wall", "Wall.", NULL, NULL }; 95 struct tile tile_horizontalwall = { '-', Blocks, "Wall", "Wall.", NULL, NULL }; 96 struct tile tile_door = { '/', 0, "Door", "A door.", NULL, NULL }; 97 struct tile tile_bookshelf = { 'E', Important, "Bookshelf", "A bookshelf.", "A loading bar?! In a book?!", interactmenu }; 98 struct tile tile_book = { '?', Important, "%s", "A book: '%s'.", "A loading bar?! In a book?!", interactitem }; 99 struct tile tile_portal = { '0', Important, "%s", "A portal: '%s'.", "You are getting transported through time and space.", interactitem }; 100 struct tile tile_portalmachine = { 'O', Important, "Portal Machine", "A portal machine.", "You are getting transported through time and space.", interactmenu }; 101 struct tile tile_heapofstuff = { '%', Important, "Heap", "A heap of stuff.", "The thing you touches glows strangely...", interactmenu }; 102 struct tile tile_elevator = { 'L', Important, "Elevator", "An elevator.", "You hear elevator music...", interactmenu }; 103 struct tile tile_stairsdown = { '>', Important, "'%s'", "A staircase leading down: '%s'.", "Too many stairs...", interactitem }; 104 105 struct tile tile_stairsup = { '<', Standout | Important, "'%s'", "A staircase leading up: '%s'.", "Too many stairs...", interactitem }; 106 struct tile tile_backportal = { '0', Standout | Important, "'%s'", "A portal leading back to wherever you came from: '%s'.", "You are getting transported through time and space.", interactitem }; 107 108 void drawscreen(void); 109 110 int 111 mygetchar_(void) 112 { 113 int r; 114 fd_set fdset; 115 116 FD_ZERO(&fdset); 117 FD_SET(0, &fdset); 118 119 if ((r = select(1, &fdset, NULL, NULL, NULL)) == -1) { 120 if (errno == EINTR) 121 return -1; 122 return -2; 123 } 124 125 return getchar(); 126 } 127 128 volatile sig_atomic_t sigwinch; 129 130 int 131 mygetchar(void) 132 { 133 int r; 134 135 while ((r = mygetchar_()) == -1) { 136 if (sigwinch) { 137 sigwinch = 0; 138 139 if (termset == OK) 140 del_curterm(cur_term); 141 termset = setupterm(NULL, 1, NULL); 142 143 drawscreen(); 144 } 145 } 146 147 if (r == -2) 148 die("mygetchar: %s", strerror(errno)); 149 150 return r; 151 } 152 153 /* 154 FNV-1a ( http://www.isthe.com/chongo/tech/comp/fnv/ ) 155 FNV was published into the public domain ( https://creativecommons.org/publicdomain/zero/1.0/ ) 156 by Landon Curt Noll: http://www.isthe.com/chongo/tech/comp/fnv/#public_domain 157 */ 158 uint32_t 159 fnv1a(int n,...) 160 { 161 int i; 162 char *s; 163 va_list l; 164 uint32_t h; 165 166 h = 0x811c9dc5; 167 168 va_start(l, n); 169 for (i = 0; i < n; i++) { 170 for (s = va_arg(l, char*); *s; s++) { 171 h ^= *s; 172 h *= 0x01000193; 173 } 174 } 175 va_end(l); 176 177 return h; 178 } 179 180 /* 181 An LCG using the constants from "Numerical Recipes". 182 */ 183 uint16_t 184 ranqd1(uint32_t *s) 185 { 186 return (*s = 1664525 * (*s) + 1013904223) >> 16; 187 } 188 189 struct rect * 190 randomneighbor(struct rect *x, struct rect *rs, uint32_t *prng, int (*filter)(struct rect *, struct rect *)) 191 { 192 struct rect *r, *result; 193 size_t n; 194 195 n = 0; 196 result = NULL; 197 for (r = rs; r; r = r->next) { 198 if (r == x) 199 continue; 200 if (r->y2 < x->y1 || r->y1 > x->y2 || r->x2 < x->x1 || r->x1 > x->x2) 201 continue; 202 if ((r->y2 == x->y1 || r->y1 == x->y2) && (r->x2 == x->x1 || r->x1 == x->x2)) 203 continue; 204 if (!filter(x, r)) 205 continue; 206 n++; 207 if (ranqd1(prng) / (1. + UINT16_MAX) < 1. / n) 208 result = r; 209 } 210 211 return result; 212 } 213 214 size_t 215 min(size_t a, size_t b) 216 { 217 if (a < b) 218 return a; 219 return b; 220 } 221 222 size_t 223 max(size_t a, size_t b) 224 { 225 if (a > b) 226 return a; 227 return b; 228 } 229 230 /* 231 Creates an uneven grid by splitting the map recursively. 232 Returns an array containing the cells (rects) of the grid. 233 */ 234 struct rect * 235 generaterects(size_t heightmin, size_t widthmin, uint32_t prng) 236 { 237 struct rect *queuehead, *queuetail; 238 struct rect *r, *t; 239 struct rect *rects; 240 size_t w, h; 241 int vertical, spaceforvertical, spaceforhorizontal; 242 243 r = malloc(sizeof(*r)); 244 memset(r, 0, sizeof(*r)); 245 r->x1 = r->y1 = 0; 246 r->x2 = MAPWIDTH; 247 r->y2 = MAPHEIGHT; 248 r->d = 0; 249 250 queuetail = r; 251 queuetail->next = NULL; 252 queuehead = r; 253 254 rects = NULL; 255 256 while (queuehead) { 257 r = queuehead; 258 if (queuetail == queuehead) 259 queuetail = NULL; 260 queuehead = queuehead->next; 261 262 spaceforvertical = r->y2 - r->y1 >= heightmin * 2; 263 spaceforhorizontal = r->x2 - r->x1 >= widthmin * 2; 264 265 if (spaceforhorizontal && spaceforvertical) { 266 vertical = ranqd1(&prng) & 1; 267 } else if (spaceforhorizontal) { 268 vertical = 0; 269 } else if (spaceforvertical) { 270 vertical = 1; 271 } else { 272 r->next = rects; 273 rects = r; 274 continue; 275 } 276 277 if (vertical) { 278 w = r->x2 - r->x1; 279 h = heightmin + ranqd1(&prng) % (1 + r->y2 - r->y1 - heightmin * 2); 280 } else { 281 w = widthmin + ranqd1(&prng) % (1 + r->x2 - r->x1 - widthmin * 2); 282 h = r->y2 - r->y1; 283 } 284 285 t = malloc(sizeof(*t)); 286 memset(t, 0, sizeof(*t)); 287 t->x1 = r->x1; 288 t->y1 = r->y1; 289 t->x2 = r->x1 + w; 290 t->y2 = r->y1 + h; 291 t->d = r->d + 1; 292 293 if (!queuetail) { 294 queuehead = t; 295 queuetail = t; 296 } else { 297 queuetail->next = t; 298 queuetail = t; 299 } 300 301 t = malloc(sizeof(*t)); 302 memset(t, 0, sizeof(*t)); 303 if (vertical) { 304 t->x1 = r->x1; 305 t->y1 = r->y1 + h; 306 } else { 307 t->x1 = r->x1 + w; 308 t->y1 = r->y1; 309 } 310 t->x2 = r->x2; 311 t->y2 = r->y2; 312 t->d = r->d + 1; 313 314 queuetail->next = t; 315 queuetail = t; 316 317 free(r); 318 } 319 320 return rects; 321 } 322 323 void 324 connectpoints_horizontal(size_t y, 325 size_t ax, int ea, struct tile *at, 326 size_t bx, int eb, struct tile *bt, 327 struct tile *t) 328 { 329 size_t i, s, e; 330 ssize_t ii; 331 332 if (ax < bx) 333 ii = 1; 334 else if (ax > bx) 335 ii = -1; 336 else 337 ii = 0; 338 339 s = ax; 340 if (ea) 341 s += ii; 342 e = bx + ii; 343 if (eb) 344 e -= ii; 345 346 for (i = s; i != e; i += ii) 347 map[y][i].tile = t; 348 349 if (e - ii == s) { 350 if (at != t) 351 map[y][s].tile = at; 352 if (bt != t) 353 map[y][s].tile = bt; 354 } else { 355 map[y][s].tile = at; 356 map[y][e - ii].tile = bt; 357 } 358 } 359 360 void 361 connectpoints_vertical(size_t x, 362 size_t ay, int ea, struct tile *at, 363 size_t by, int eb, struct tile *bt, 364 struct tile *t) 365 { 366 size_t i, s, e; 367 ssize_t ii; 368 369 if (ay < by) 370 ii = 1; 371 else if (ay > by) 372 ii = -1; 373 else 374 ii = 0; 375 376 s = ay; 377 if (ea) 378 s += ii; 379 e = by + ii; 380 if (eb) 381 e -= ii; 382 383 for (i = s; i != e; i += ii) 384 map[i][x].tile = t; 385 386 if (e - ii == s) { 387 if (at != t) 388 map[s][x].tile = at; 389 if (bt != t) 390 map[s][x].tile = bt; 391 } else { 392 map[s][x].tile = at; 393 map[e - ii][x].tile = bt; 394 } 395 } 396 397 void 398 connectpoints(size_t ax, size_t ay, int ea, struct tile *at, 399 size_t bx, size_t by, int eb, struct tile *bt, 400 int vertical, struct tile *ct) 401 { 402 if (!vertical) { 403 connectpoints_horizontal(ay, 404 ax, ea, at, 405 bx, 0, ct, 406 ct); 407 connectpoints_vertical(bx, 408 ay, 0, ct, 409 by, eb, bt, 410 ct); 411 } else { 412 connectpoints_vertical(ax, 413 ay, ea, at, 414 by, 0, ct, 415 ct); 416 connectpoints_horizontal(by, 417 ax, 0, ct, 418 bx, eb, bt, 419 ct); 420 } 421 } 422 423 void 424 nearestpoints(struct room *a, struct room *b, size_t *ax, size_t *ay, size_t *bx, size_t *by) 425 { 426 if (a->y >= b->y && a->y < b->y + b->h) { 427 *ay = *by = a->y; 428 } else if (b->y >= a->y && b->y < a->y + a->h) { 429 *ay = *by = b->y; 430 } else if (a->y >= b->y) { 431 *ay = a->y; 432 *by = b->y + b->h - 1; 433 } else if (b->y >= a->y) { 434 *ay = a->y + a->h - 1; 435 *by = b->y; 436 } 437 438 if (a->x >= b->x && a->x < b->x + b->w) { 439 *ax = *bx = a->x; 440 } else if (b->x >= a->x && b->x < a->x + a->w) { 441 *ax = *bx = b->x; 442 } else if (a->x >= b->x) { 443 *ax = a->x; 444 *bx = b->x + b->w - 1; 445 } else if (b->x >= a->x) { 446 *ax = a->x + a->w - 1; 447 *bx = b->x; 448 } 449 } 450 451 void 452 connectadjacentrooms(struct rect *a, struct room *ar, struct rect *b, struct room *br) 453 { 454 size_t irx1, iry1, irx2, iry2; 455 size_t rx1, ry1, rx2, ry2; 456 size_t cx, cy; 457 struct rect *r1, *r2; 458 struct room *room1, *room2; 459 int vertical; 460 461 if (a->x2 == b->x1) { 462 r1 = a; 463 room1 = ar; 464 r2 = b; 465 room2 = br; 466 } else if (b->x2 == a->x1) { 467 r1 = b; 468 room1 = br; 469 r2 = a; 470 room2 = ar; 471 } else if (a->y2 == b->y1) { 472 r1 = a; 473 room1 = ar; 474 r2 = b; 475 room2 = br; 476 } else if (b->y2 == a->y1) { 477 r1 = b; 478 room1 = br; 479 room2 = ar; 480 r2 = a; 481 } else { 482 return; 483 } 484 485 if (r1->y2 == r2->y1) { 486 irx1 = max(r1->x1, r2->x1); 487 irx2 = min(r1->x2, r2->x2); 488 iry1 = r1->y2; 489 iry2 = r1->y2 + 1; 490 } else { 491 iry1 = max(r1->y1, r2->y1); 492 iry2 = min(r1->y2, r2->y2); 493 irx1 = r1->x2; 494 irx2 = r1->x2 + 1; 495 } 496 497 nearestpoints(room1, room2, &rx1, &ry1, &rx2, &ry2); 498 499 if (r1->y2 == r2->y1) { 500 /* both points are in the intersection */ 501 if (rx1 >= irx1 && rx1 < irx2 && 502 rx2 >= irx1 && rx2 < irx2) { 503 vertical = 1; 504 cx = (rx2 + rx1) / 2; 505 cy = (ry2 + ry1) / 2; 506 } else 507 /* none is in the intersection */ 508 if (!(rx1 >= irx1 && rx1 < irx2) && 509 !(rx2 >= irx1 && rx2 < irx2)) { 510 vertical = 0; 511 cx = irx1; 512 cy = r1->y2; 513 } else if (rx1 >= irx1 && rx1 < irx2) { 514 vertical = 1; 515 cx = (rx2 + rx1) / 2; 516 cy = r1->y2; 517 } else if (rx2 >= irx1 && rx2 < irx2) { 518 vertical = 1; 519 cx = rx2; 520 cy = r1->y2 - 1; 521 } 522 } else { 523 /* both points are in the intersection */ 524 if (ry1 >= iry1 && ry1 < iry2 && 525 ry2 >= iry1 && ry2 < iry2) { 526 vertical = 0; 527 cx = (rx2 + rx1) / 2; 528 cy = (ry2 + ry1) / 2; 529 } else 530 /* none is in the intersection */ 531 if (!(ry1 >= iry1 && ry1 < iry2) && 532 !(ry2 >= iry1 && ry2 < iry2)) { 533 vertical = 1; 534 cx = r1->x2; 535 cy = iry1; 536 } else if (ry1 >= iry1 && ry1 < iry2) { 537 vertical = 0; 538 cx = r1->x2; 539 cy = (ry2 + ry1) / 2; 540 } else if (ry2 >= iry1 && ry2 < iry2) { 541 vertical = 0; 542 cx = r1->x2 - 1; 543 cy = ry2; 544 } 545 } 546 547 if (rx1 == rx2) { 548 connectpoints_vertical(rx1, 549 ry1, 1, &tile_door, 550 ry2, 1, &tile_door, 551 &tile_corridor); 552 } else if (ry1 == ry2) { 553 connectpoints_horizontal(ry1, 554 rx1, 1, &tile_door, 555 rx2, 1, &tile_door, 556 &tile_corridor); 557 } else { 558 connectpoints(rx1, ry1, 1, &tile_door, 559 cx, cy, 0, &tile_corridor, 560 vertical, &tile_corridor); 561 connectpoints(cx, cy, 1, &tile_corridor, 562 rx2, ry2, 1, &tile_door, 563 !vertical, &tile_corridor); 564 } 565 } 566 567 int 568 rectisfull(struct rect *x, struct rect *r) 569 { 570 return !!r->data.i; 571 } 572 573 int 574 rectisempty(struct rect *x, struct rect *r) 575 { 576 return !r->data.i; 577 } 578 579 int 580 rectisnotp(struct rect *x, struct rect *r) 581 { 582 return r->data.p && x->p != r && r->p != x; 583 } 584 585 int 586 rectisrandom(struct rect *x, struct rect *r) 587 { 588 return 1; 589 } 590 591 /* 592 Basically https://www.roguebasin.com/index.php/Diffusion-limited_aggregation 593 Returns the list of carved rooms. 594 */ 595 struct rect * 596 dla(struct rect *rects, size_t l, uint32_t prng) { 597 size_t rl, i, n; 598 struct rect *r, *t, *walk, *p; 599 600 for (r = rects, rl = 0; r; r = r->next) 601 rl++; 602 603 if (l > rl) 604 l = rl; 605 606 /* get the rect which contains the map center */ 607 for (r = rects; r; r = r->next) { 608 if (MAPHEIGHT / 2 >= r->y1 && MAPHEIGHT / 2 < r->y2 && 609 MAPWIDTH / 2 >= r->x1 && MAPWIDTH / 2 < r->x2) 610 break; 611 } 612 613 p = NULL; 614 walk = NULL; 615 i = 0; 616 for (;;) { 617 r->p = p; 618 r->data.i = 1; 619 r->next2 = walk; 620 walk = r; 621 622 if (i >= l - 1) 623 break; 624 625 t = NULL; 626 for (r = rects, n = 0; r; r = r->next) { 627 if (r->data.i) 628 continue; 629 n++; 630 if (ranqd1(&prng) / (1. + UINT16_MAX) < 1. / n) 631 t = r; 632 } 633 634 /* there is no free rect left */ 635 if (!t) 636 break; 637 638 /* do a random walk starting from t until the walk collides with a carved room (r) */ 639 while ((r = randomneighbor(t, rects, &prng, rectisrandom)) && !r->data.i) 640 t = r; 641 642 p = r; 643 r = t; 644 645 i++; 646 } 647 648 return walk; 649 } 650 651 void 652 rendermapchar(size_t i, size_t j) { 653 if (map[i][j].tile->flags & Standout) 654 putp(tiparm(enter_standout_mode)); 655 putchar(map[i][j].tile->c); 656 if (map[i][j].tile->flags & Standout) 657 putp(tiparm(exit_standout_mode)); 658 } 659 660 void 661 rendermapline(size_t i) 662 { 663 size_t j; 664 665 for (j = ox; j < min(MAPWIDTH, ox + columns); j++) 666 rendermapchar(i, j); 667 } 668 669 void 670 rendermap(void) 671 { 672 size_t i; 673 674 if (px < columns / 2 || MAPWIDTH <= columns) 675 ox = 0; 676 else if (px >= MAPWIDTH - columns / 2 - 1) 677 ox = MAPWIDTH - columns; 678 else 679 ox = px - columns / 2; 680 681 if (py < maplines / 2 || MAPHEIGHT <= maplines) 682 oy = 0; 683 else if (py >= MAPHEIGHT - maplines / 2 - 1) 684 oy = MAPHEIGHT - maplines; 685 else 686 oy = py - maplines / 2; 687 688 for (i = oy; i < min(MAPHEIGHT, oy + (lines - 2)); i++) { 689 if (i != oy) 690 putp(tiparm(cursor_down)); 691 rendermapline(i); 692 } 693 } 694 695 size_t 696 placeitems_hash(Item *item, size_t *assocs, size_t k) 697 { 698 Dir *dir; 699 Item *citem; 700 size_t i; 701 702 dir = item->dat; 703 for (i = 0; i < dir->nitems; i++) { 704 citem = &dir->items[i]; 705 /* TODO Somewhere else */ 706 if (!citem->host || !citem->port || !citem->selector) 707 continue; 708 assocs[i] = fnv1a(6, item->host, item->port, item->selector, citem->host, citem->port, citem->selector) % k; 709 } 710 711 return k; 712 } 713 714 #define POSITIONS_LENGTH 5 715 enum { 716 Portal, 717 StaircaseDown, 718 Bookshelf, 719 OtherStuff, 720 Back 721 }; 722 723 enum { 724 FillEntireCell = 1 725 }; 726 727 #define length(a) (sizeof(a) / sizeof(a[0])) 728 static struct dungeontype { 729 char *name; 730 char flags; 731 size_t heightmin; 732 size_t heightmax; 733 size_t widthmin; 734 size_t widthmax; 735 size_t margin; 736 size_t wiggle; 737 } dungeontypes[] = { 738 { "rogueish", 0, 2, 5, 3, 7, 2, 0 }, 739 { "rogueish-wide", 0, 2, 5, 3, 7, 3, 1 }, 740 { "compact", FillEntireCell, 2, 2, 3, 3, 1, 0 }, 741 }; 742 743 void 744 generatemap(Item *item, Item *pitem) 745 { 746 Dir *dir; 747 Item *citem; 748 size_t l, i, j, k, ir, n, m, x, y; 749 struct rect *rects, *walk, *tr, *cr; 750 struct room *rooms, *room; 751 size_t *cassocs; 752 int changedlevel, gonedown; 753 char buffer[10]; 754 uint32_t prng; 755 struct { 756 size_t x, y; 757 } positions[POSITIONS_LENGTH]; 758 size_t cellwidth, cellheight; 759 struct dungeontype *type; 760 761 type = &dungeontypes[fnv1a(3, item->host, item->port, "dungeontype") % length(dungeontypes)]; 762 763 cellheight = type->heightmin + 2 * type->margin + type->wiggle; 764 cellwidth = type->widthmin + 2 * type->margin + type->wiggle; 765 766 rects = generaterects(cellheight, cellwidth, fnv1a(4, item->host, item->port, item->selector, "gridseed")); 767 768 dir = item->dat; 769 for (j = l = 0; j < dir->nitems; j++) { 770 if (dir->items[j].type != 0 && 771 dir->items[j].type != 'i' && 772 dir->items[j].type != '3') 773 l++; 774 } 775 776 k = 1 + l / 10; 777 walk = dla(rects, k, fnv1a(4, item->host, item->port, item->selector, "randomwalkseed")); 778 for (cr = walk, k = 0; cr; cr = cr->next2, k++); 779 780 for (cr = rects; cr; cr = cr->next) 781 cr->data.p = NULL; 782 783 rooms = calloc(k, sizeof(*rooms)); 784 for (cr = walk, i = 0; cr; cr = cr->next2, i++) 785 cr->data.p = &rooms[i]; 786 787 prng = fnv1a(4, item->host, item->port, item->selector, "roomsseed"); 788 for (cr = walk; cr; cr = cr->next2) { 789 room = cr->data.p; 790 791 if (type->flags & FillEntireCell) { 792 room->w = cr->x2 - cr->x1 - 2 * type->margin; 793 room->x = cr->x1 + type->margin; 794 room->h = cr->y2 - cr->y1 - 2 * type->margin; 795 room->y = cr->y1 + type->margin; 796 } else { 797 room->w = type->widthmin + ranqd1(&prng) % (1 + min(cr->x2 - cr->x1 - type->widthmin - 2 * type->margin, type->widthmax - type->widthmin)); 798 room->x = cr->x1 + type->margin + ranqd1(&prng) % (1 + cr->x2 - cr->x1 - room->w - 2 * type->margin); 799 room->h = type->heightmin + ranqd1(&prng) % (1 + min(cr->y2 - cr->y1 - type->heightmin - 2 * type->margin, type->heightmax - type->heightmin)); 800 room->y = cr->y1 + type->margin + ranqd1(&prng) % (1 + cr->y2 - cr->y1 - room->h - 2 * type->margin); 801 } 802 } 803 804 for (i = 0; i < MAPHEIGHT; i++) { 805 for (j = 0; j < MAPWIDTH; j++) { 806 map[i][j].tile = &tile_void; 807 free(map[i][j].items); 808 map[i][j].items = NULL; 809 map[i][j].nitems = 0; 810 } 811 } 812 813 for (cr = walk; cr; cr = cr->next2) { 814 room = cr->data.p; 815 816 for (x = room->x - 1; x < room->x + room->w + 1; x++) 817 map[room->y-1][x].tile = &tile_horizontalwall; 818 for (y = room->y; y < room->y + room->h; y++) { 819 map[y][room->x - 1].tile = &tile_verticalwall; 820 for (x = room->x; x < room->x + room->w; x++) 821 map[y][x].tile = &tile_floor; 822 map[y][room->x + room->w].tile = &tile_verticalwall; 823 } 824 for (x = room->x - 1; x < room->x + room->w + 1; x++) 825 map[room->y + room->h][x].tile = &tile_horizontalwall; 826 } 827 828 for (cr = walk; cr; cr = cr->next2) { 829 if (cr->p) 830 connectadjacentrooms(cr, cr->data.p, 831 cr->p, cr->p->data.p); 832 833 /* Add some loop possibility */ 834 if (tr = randomneighbor(cr, rects, &prng, rectisnotp)) 835 connectadjacentrooms(cr, cr->data.p, 836 tr, tr->data.p); 837 } 838 839 cassocs = calloc(dir->nitems, sizeof(*cassocs)); 840 841 k = placeitems_hash(item, cassocs, k); 842 843 changedlevel = item != pitem; 844 gonedown = pitem == item->entry; 845 846 /* 847 Insert items 848 The placement of items affects the initial placement of the 849 player, because they could have gone back to this map, so they 850 should appear at the elevator/portal/stair they used. 851 */ 852 853 /* 854 The initial room is everytime the first one. Reason: The count 855 of rooms is based on how many entries are in the gophermap and 856 how many rooms can fit on the map. There will be at minimum 1. 857 So when more entries get added there will be more rooms but the 858 first one stays at the same position. I think about the 859 retrying and clownflare things on bitreich.org, the selector 860 doesn't change... 861 */ 862 ir = 0; 863 864 for (i = 0; i < k; i++) { 865 /* select random positions for different item types inside the current room */ 866 snprintf(buffer, sizeof(buffer), "%lu", i); 867 prng = fnv1a(4, item->host, item->port, item->selector, buffer); 868 for (j = 0, m = rooms[i].h * rooms[i].w; j < m; j++) { 869 n = j; 870 if (j >= POSITIONS_LENGTH) 871 n *= ranqd1(&prng) / (double)UINT16_MAX; 872 873 if (n < POSITIONS_LENGTH) { 874 positions[n].x = rooms[i].x + j % rooms[i].w; 875 positions[n].y = rooms[i].y + j / rooms[i].w; 876 } 877 } 878 879 for (j = 0; j < dir->nitems; j++) { 880 if (cassocs[j] != i) 881 continue; 882 883 citem = &dir->items[j]; 884 switch (citem->type) { 885 case '0': 886 x = positions[Bookshelf].x; 887 y = positions[Bookshelf].y; 888 if (map[y][x].nitems) 889 map[y][x].tile = &tile_bookshelf; 890 else 891 map[y][x].tile = &tile_book; 892 break; 893 case '1': 894 if (strcmp(citem->host, item->host) || strcmp(citem->port, item->port)) { 895 x = positions[Portal].x; 896 y = positions[Portal].y; 897 if (map[y][x].nitems) 898 map[y][x].tile = &tile_portalmachine; 899 else 900 map[y][x].tile = &tile_portal; 901 } else { 902 x = positions[StaircaseDown].x; 903 y = positions[StaircaseDown].y; 904 if (map[y][x].nitems) 905 map[y][x].tile = &tile_elevator; 906 else 907 map[y][x].tile = &tile_stairsdown; 908 } 909 break; 910 case 0: 911 case 'i': 912 case '3': 913 continue; 914 break; 915 default: 916 x = positions[OtherStuff].x; 917 y = positions[OtherStuff].y; 918 map[y][x].tile = &tile_heapofstuff; 919 break; 920 } 921 922 map[y][x].nitems++; 923 map[y][x].items = realloc(map[y][x].items, map[y][x].nitems * sizeof(*map[y][x].items)); 924 map[y][x].items[map[y][x].nitems-1] = citem; 925 926 if (changedlevel && citem == pitem) { 927 px = x; 928 py = y; 929 } 930 } 931 932 if (i == ir && item->entry != item) { 933 y = positions[Back].y; 934 x = positions[Back].x; 935 if (strcmp(item->entry->host, item->host) || strcmp(item->entry->port, item->port)) 936 map[y][x].tile = &tile_backportal; 937 else 938 map[y][x].tile = &tile_stairsup; 939 map[y][x].nitems++; 940 map[y][x].items = realloc(map[y][x].items, map[y][x].nitems * sizeof(*map[y][x].items)); 941 map[y][x].items[map[y][x].nitems-1] = item->entry; 942 } 943 944 if (changedlevel && i == ir && (gonedown || !pitem)) { 945 px = positions[Back].x; 946 py = positions[Back].y; 947 } 948 } 949 950 free(cassocs); 951 free(rooms); 952 953 for (cr = rects; cr;) { 954 tr = cr; 955 cr = cr->next; 956 free(tr); 957 } 958 } 959 960 void 961 uisetup(void) 962 { 963 tcgetattr(0, &tsave); 964 tsacc = tsave; 965 tsacc.c_lflag &= ~(ECHO|ICANON); 966 tsacc.c_cc[VMIN] = 1; 967 tsacc.c_cc[VTIME] = 0; 968 tcsetattr(0, TCSANOW, &tsacc); 969 970 if (termset != OK) 971 /* setupterm call exits on error */ 972 termset = setupterm(NULL, 1, NULL); 973 putp(tiparm(clear_screen)); 974 fflush(stdout); 975 } 976 977 void 978 uicleanup(void) 979 { 980 tcsetattr(0, TCSANOW, &tsave); 981 982 if (termset != OK) 983 return; 984 985 putp(tiparm(change_scroll_region, 0, lines-1)); 986 putp(tiparm(clear_screen)); 987 fflush(stdout); 988 } 989 990 char * 991 uiprompt(char *fmt, ...) 992 { 993 va_list ap; 994 char *input = NULL; 995 size_t n; 996 ssize_t r; 997 998 putp(tiparm(save_cursor)); 999 1000 putp(tiparm(cursor_address, lines-1, 0)); 1001 putp(tiparm(clr_eol)); 1002 1003 va_start(ap, fmt); 1004 vsnprintf(bufout, sizeof(bufout), fmt, ap); 1005 va_end(ap); 1006 1007 n = mbsprint(bufout, columns); 1008 1009 putp(tiparm(clr_eol)); 1010 1011 putp(tiparm(cursor_address, lines-1, n)); 1012 1013 tsacc.c_lflag |= (ECHO|ICANON); 1014 tcsetattr(0, TCSANOW, &tsacc); 1015 fflush(stdout); 1016 1017 n = 0; 1018 r = getline(&input, &n, stdin); 1019 1020 tsacc.c_lflag &= ~(ECHO|ICANON); 1021 tcsetattr(0, TCSANOW, &tsacc); 1022 putp(tiparm(restore_cursor)); 1023 fflush(stdout); 1024 1025 if (r == -1 || feof(stdin)) { 1026 clearerr(stdin); 1027 clear(&input); 1028 } else if (input[r - 1] == '\n') { 1029 input[--r] = '\0'; 1030 } 1031 1032 return input; 1033 } 1034 1035 void 1036 displaybar(char *s) { 1037 size_t n; 1038 1039 putp(tiparm(save_cursor)); 1040 1041 putp(tiparm(cursor_address, lines-2, 0)); 1042 putp(tiparm(enter_standout_mode)); 1043 1044 n = mbsprint(s, columns); 1045 for (n = columns - n; n; n--) 1046 putchar(' '); 1047 1048 putp(tiparm(exit_standout_mode)); 1049 1050 putp(tiparm(restore_cursor)); 1051 fflush(stdout); 1052 } 1053 1054 void 1055 vdisplayinfoline(char *fmt, va_list ap) 1056 { 1057 putp(tiparm(save_cursor)); 1058 1059 putp(tiparm(cursor_address, lines-1, 0)); 1060 1061 vsnprintf(bufout, sizeof(bufout), fmt, ap); 1062 1063 mbsprint(bufout, columns); 1064 1065 putp(tiparm(clr_eol)); 1066 1067 putp(tiparm(restore_cursor)); 1068 fflush(stdout); 1069 } 1070 1071 void 1072 uistatus(char *fmt, ...) 1073 { 1074 va_list ap; 1075 size_t n; 1076 1077 putp(tiparm(save_cursor)); 1078 1079 putp(tiparm(cursor_address, lines-1, 0)); 1080 1081 va_start(ap, fmt); 1082 n = vsnprintf(bufout, sizeof(bufout), fmt, ap); 1083 va_end(ap); 1084 1085 if (n < sizeof(bufout)-1) { 1086 snprintf(bufout+n, sizeof(bufout)-n, 1087 " [Press a key to continue \xe2\x98\x83]"); 1088 } 1089 1090 mbsprint(bufout, columns); 1091 1092 putp(tiparm(clr_eol)); 1093 1094 putp(tiparm(restore_cursor)); 1095 fflush(stdout); 1096 1097 mygetchar(); 1098 } 1099 1100 void 1101 displayinfoline(char *fmt, ...) 1102 { 1103 va_list ap; 1104 1105 va_start(ap, fmt); 1106 vdisplayinfoline(fmt, ap); 1107 va_end(ap); 1108 } 1109 1110 char *menutitle; 1111 Item **menuitems; 1112 size_t menunitems; 1113 volatile size_t menuoffset; 1114 size_t menuselected; 1115 1116 void 1117 menudraw(void) 1118 { 1119 size_t i, n; 1120 1121 putp(tiparm(change_scroll_region, 1, lines-1)); 1122 putp(tiparm(clear_screen)); 1123 1124 putp(tiparm(enter_standout_mode)); 1125 puts(menutitle); 1126 putp(tiparm(exit_standout_mode)); 1127 1128 if (menuselected - menuoffset >= lines - 1) 1129 menuoffset = menuselected - (lines - 1) + 1; 1130 1131 for (i = menuoffset, n = 0; i < menunitems && n < lines - 1; i++, n++) { 1132 if (i != menuoffset) 1133 putp(tiparm(cursor_down)); 1134 if (i == menuselected) 1135 putp(tiparm(enter_standout_mode)); 1136 mbsprint(menuitems[i]->username, columns); 1137 if (i == menuselected) 1138 putp(tiparm(exit_standout_mode)); 1139 putp(tiparm(column_address, 0)); 1140 } 1141 fflush(stdout); 1142 } 1143 1144 Item * 1145 showmenu(char *title, Item **item, size_t l) 1146 { 1147 Item *selection; 1148 1149 menutitle = title; 1150 menuitems = item; 1151 menunitems = l; 1152 menuselected = 0; 1153 menuoffset = 0; 1154 screen = MenuScreen; 1155 drawscreen(); 1156 1157 selection = NULL; 1158 for (;;) { 1159 switch (mygetchar()) { 1160 case 'j': 1161 if (menuselected + 1 < menunitems) { 1162 putp(tiparm(cursor_address, 1 + menuselected - menuoffset, 0)); 1163 mbsprint(menuitems[menuselected]->username, columns); 1164 menuselected++; 1165 putp(tiparm(column_address, 0)); 1166 if (menuselected - menuoffset >= lines - 1) { 1167 menuoffset++; 1168 putp(tiparm(scroll_forward)); 1169 } else { 1170 putp(tiparm(cursor_down)); 1171 } 1172 putp(tiparm(enter_standout_mode)); 1173 mbsprint(menuitems[menuselected]->username, columns); 1174 putp(tiparm(exit_standout_mode)); 1175 } 1176 break; 1177 case 'k': 1178 if (menuselected > 0) { 1179 putp(tiparm(cursor_address, 1 + menuselected - menuoffset, 0)); 1180 mbsprint(menuitems[menuselected]->username, columns); 1181 menuselected--; 1182 putp(tiparm(column_address, 0)); 1183 if (menuselected < menuoffset) { 1184 menuoffset = menuselected; 1185 putp(tiparm(scroll_reverse)); 1186 } else { 1187 putp(tiparm(cursor_up)); 1188 } 1189 putp(tiparm(enter_standout_mode)); 1190 mbsprint(menuitems[menuselected]->username, columns); 1191 putp(tiparm(exit_standout_mode)); 1192 } 1193 break; 1194 case ' ': 1195 selection = menuitems[menuselected]; 1196 case 0x1b: 1197 goto endloop; 1198 break; 1199 } 1200 fflush(stdout); 1201 } 1202 1203 endloop: 1204 screen = DungeonScreen; 1205 drawscreen(); 1206 1207 return selection; 1208 } 1209 1210 Item * 1211 interactitem(struct cell *c) 1212 { 1213 displayinfoline(map[py][px].tile->afterinteract); 1214 1215 return map[py][px].items[0]; 1216 } 1217 1218 Item * 1219 interactmenu(struct cell *c) 1220 { 1221 Item *selection; 1222 1223 if (selection = showmenu(map[py][px].tile->name, map[py][px].items, map[py][px].nitems)) 1224 displayinfoline(map[py][px].tile->afterinteract); 1225 1226 return selection; 1227 } 1228 1229 Item * 1230 interact(Item *item) 1231 { 1232 if (map[py][px].tile->interact) 1233 return map[py][px].tile->interact(&map[py][px]); 1234 1235 return NULL; 1236 } 1237 1238 void 1239 describe(size_t x, size_t y, int verbose) 1240 { 1241 char *name; 1242 1243 if (map[y][x].nitems) { 1244 if (*map[y][x].items[0]->username) { 1245 name = map[y][x].items[0]->username; 1246 } else { 1247 itemuri(map[y][x].items[0], bufout2, sizeof(bufout2)); 1248 name = bufout2; 1249 } 1250 } else { 1251 name = NULL; 1252 } 1253 if (map[y][x].tile->flags & Important || verbose) 1254 displayinfoline(map[y][x].tile->description, name); 1255 else 1256 displayinfoline(""); 1257 } 1258 1259 void 1260 dungeondraw(void) 1261 { 1262 putp(tiparm(change_scroll_region, 0, lines-3)); 1263 putp(tiparm(clear_screen)); 1264 1265 rendermap(); 1266 1267 putp(tiparm(cursor_address, py - oy, px - ox)); 1268 putchar('@'); 1269 putp(tiparm(cursor_address, py - oy, px - ox)); 1270 1271 if (curentry->entry != curentry) { 1272 displaybar(curentry->username); 1273 } else { 1274 itemuri(curentry, bufout, sizeof(bufout)); 1275 displaybar(bufout); 1276 } 1277 1278 describe(px, py, 0); 1279 } 1280 1281 void 1282 move(ssize_t dx, ssize_t dy) 1283 { 1284 ssize_t i; 1285 size_t x, y; 1286 size_t noy, nox; 1287 1288 if ((ssize_t)py + dy >= MAPHEIGHT || (ssize_t)py + dy < 0) 1289 return; 1290 if ((ssize_t)px + dx >= MAPWIDTH || (ssize_t)px + dx < 0) 1291 return; 1292 1293 x = px + dx; 1294 y = py + dy; 1295 1296 if (map[y][x].tile->flags & Blocks) 1297 return; 1298 1299 if (dx) { 1300 if (x < columns / 2 || MAPWIDTH <= columns) 1301 nox = 0; 1302 else if (x >= MAPWIDTH - columns / 2 - 1) 1303 nox = MAPWIDTH - columns; 1304 else 1305 nox = x - columns / 2; 1306 1307 if (ox != nox) { 1308 putp(tiparm(cursor_address, 0, 0)); 1309 rendermap(); 1310 } else { 1311 putp(tiparm(cursor_address, py - oy, px - ox)); 1312 rendermapchar(py, px); 1313 } 1314 } else if (dy) { 1315 putp(tiparm(cursor_address, py - oy, px - ox)); 1316 rendermapchar(py, px); 1317 1318 if (y < maplines / 2 || MAPHEIGHT <= maplines) { 1319 noy = 0; 1320 } else if (y >= MAPHEIGHT - maplines / 2 - 1) { 1321 noy = MAPHEIGHT - maplines; 1322 } else { 1323 noy = y - maplines / 2; 1324 } 1325 1326 if (noy < oy) { 1327 putp(tiparm(cursor_address, 0, 0)); 1328 for (i = (ssize_t)oy - 1; i >= (ssize_t)noy; i--) { 1329 putp(tiparm(scroll_reverse)); 1330 rendermapline(i); 1331 putp(tiparm(column_address, 0)); 1332 } 1333 } else if (noy > oy) { 1334 putp(tiparm(cursor_address, lines-3, 0)); 1335 for (i = oy + 1; i <= noy; i++) { 1336 putp(tiparm(scroll_forward)); 1337 rendermapline(i + maplines - 1); 1338 putp(tiparm(column_address, 0)); 1339 } 1340 } 1341 oy = noy; 1342 } 1343 1344 py = y; 1345 px = x; 1346 putp(tiparm(cursor_address, py - oy, px - ox)); 1347 putchar('@'); 1348 putp(tiparm(cursor_address, py - oy, px - ox)); 1349 1350 describe(px, py, 0); 1351 } 1352 1353 void 1354 drawscreen(void) 1355 { 1356 switch (screen) { 1357 case DungeonScreen: 1358 dungeondraw(); 1359 break; 1360 case MenuScreen: 1361 menudraw(); 1362 break; 1363 } 1364 fflush(stdout); 1365 } 1366 1367 void 1368 uidisplay(Item *entry) 1369 { 1370 if (!entry || !(entry->type == '1' || entry->type == '+' || entry->type == '7')) 1371 return; 1372 1373 if (entry != curentry) { 1374 generatemap(entry, curentry); 1375 curentry = entry; 1376 } 1377 1378 drawscreen(); 1379 } 1380 1381 Item * 1382 uiselectitem(Item *entry) 1383 { 1384 Item *e; 1385 1386 if (!entry || !entry->dat) 1387 return NULL; 1388 1389 for (;;) { 1390 switch (mygetchar()) { 1391 case 'h': 1392 move(-1, 0); 1393 break; 1394 case 'j': 1395 move(0, 1); 1396 break; 1397 case 'k': 1398 move(0, -1); 1399 break; 1400 case 'l': 1401 move(1, 0); 1402 break; 1403 case ' ': 1404 if (e = interact(entry)) 1405 return e; 1406 break; 1407 case 'q': 1408 case 0x1b: 1409 return NULL; 1410 } 1411 fflush(stdout); 1412 } 1413 } 1414 1415 void 1416 uisigwinch(int signal) 1417 { 1418 sigwinch = 1; 1419 }