/* * Object (and inventory) common code. */ #include #include #include #include #include "vars.h" #include "math.h" #include "dice.h" #include "pline.h" #include "signals.h" #include "display.h" #include "structs.h" #include "disputil.h" #include "objtypes.h" #include "stdio-util.h" #include "obj.h" /* * The vector of characters used to name objects in inventories. The * first 26 are used alone; after that, the following 36 are used * followed by one of the full 62. That is, things go a, b, ..., y, * z, Aa, Ab, Ac, ..., Az, AA, AB, ..., AZ, A0, ..., A9, Ba, Bb, ..., * Bz, BA, ..., BZ, B0, ..., B9, Ca, ..., C9, Da, ..., Z9, 0a, ..., * 09, 1a, ..., 19, ..., 9a, ..., 99. This supports up to 2258 slots * in an inventory. Objects after the first 2258 in an inventory are * not directly accessible. */ static const char icv[62] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; /* * Shuffle the name2 and name3 strings for an object class. This is * used during startup to randomize, eg, the correspondence between * scroll titles and scroll effects. */ static void shuffle_name23(int class) { int tlist[OCC__MAX]; int tn; int i; int j; tn = 0; for (i=0;i OCC__MAX) abort(); tlist[tn++] = i; } } for (i=0;itype].ops->old)(o); free(o); } /* * Free up an INVOBJ, including all the OBJs that make it up. */ void invobjfree(INVOBJ *io) { int i; for (i=0;in;i++) objfree(io->v[i]); free(io->v); free(io); } /* * Remove an INVOBJ from an INVENT. If it's not there, nothing * happens; however, if it's there and its inventory backpointer is * wrong, we panic. */ int inv_remove(INVENT *inv, INVOBJ *io) { INVOBJ **l; INVOBJ *t; l = &inv->inv; while ((t = *l)) { if (t == io) { *l = t->link; if (t->inv != inv) panic("invobj in wrong inventory"); t->inv = 0; return(1); } l = &t->link; } return(0); } /* * Destroy an inventory, including destroying everything in it. */ void destroy_inv(INVENT *inv) { INVOBJ *io; while (inv->inv) { io = inv->inv; inv->inv = io->link; invobjfree(io); } } /* * Merge objects from one inventory (from) into another (to). After * this, from will be empty. */ void mergeinv(INVENT *from, INVENT *to) { INVOBJ *o; int i; while (from->inv) { o = from->inv; from->inv = o->link; for (i=0;in;i++) add_obj_to_inv(o->v[i],to); free(o->v); free(o); } } /* * Move a single INVOBJ (io) from one inventory (oldinv) to another * (newinv). Returns the new INVOBJ (which may or may not equal io, * because io may be merged into an INVOBJ already in newinv). */ INVOBJ *inv_move_1(INVENT *oldinv, INVOBJ *io, INVENT *newinv) { INVOBJ *p; int i; inv_remove(oldinv,io); for (i=0;in;i++) p = add_obj_to_inv(io->v[i],newinv); return(p); } /* * Make a new object of a given type. This creates a singular object; * if a plural object is desired, the number must be set elsewhere. */ OBJ *obj_make(int type) { OBJ *o; o = malloc(sizeof(OBJ)); o->type = type; o->number = 1; return((*objtypes[type].ops->new)(type,o)); } /* * Return true if two objects are collapsible. If their types are * different, they never are; otherwise, call the type's collapsible * method to see. */ static int obj_collapsible(OBJ *o1, OBJ *o2) { if (objtypes[o1->type].ops != objtypes[o2->type].ops) return(0); return((*objtypes[o1->type].ops->collapsible)(o1,o2)); } /* * Return true if two objects are identical. If their types are * different, they never are; otherwise, call the type's identical * method to see. */ static int obj_identical(OBJ *o1, OBJ *o2) { if (o1->type != o2->type) return(0); return((*objtypes[o1->type].ops->identical)(o1,o2)); } /* * Add an OBJ to an inventory, including creating an INVOBJ for it if * necessary. */ INVOBJ *add_obj_to_inv(OBJ *o, INVENT *inv) { INVOBJ *io; int i; for (io=inv->inv;io;io=io->link) { if (obj_collapsible(o,io->v[0])) { for (i=0;in;i++) { if (obj_identical(o,io->v[i])) { io->v[i]->number += o->number; io->dispn += o->number; objfree(o); return(io); } } io->n ++; io->v = realloc(io->v,io->n*sizeof(OBJ *)); io->v[io->n-1] = o; io->dispn += o->number; return(io); } } io = malloc(sizeof(INVOBJ)); io->inv = inv; io->xwi = find_xwi(inv); io->dispn = o->number; io->n = 1; io->v = malloc(sizeof(OBJ *)); io->v[0] = o; io->link = inv->inv; inv->inv = io; return(io); } /* * Return true if an object (o) is present in an inventory (inv), false * if not. */ int inv_present(INVENT *inv, OBJ *o) { INVOBJ *io; int i; for (io=inv->inv;io;io=io->link) for (i=io->n-1;i>=0;i--) if (io->v[i] == o) return(1); return(0); } /* * An inventory interest function that selects everything. */ int showinvent_all(INVOBJ *inv __attribute__((__unused__))) { return(1); } /* * Format an INVOBJ (io) for display in an inventory list, writing the * result to f. Objects after the first 2258 do not get the label * characters; they are not directly accessible. */ void format_inv_line(INVOBJ *io, FILE *f) { if (io->xwi < 26) { putc(' ',f); putc(icv[io->xwi],f); putc(')',f); putc(' ',f); } else if (io->xwi < 26+(36*62)) { putc(icv[26+((io->xwi-26)/62)],f); putc(icv[(io->xwi-26)%62],f); putc(')',f); putc(' ',f); } (*objtypes[io->v[0]->type].ops->format)(f,io); } /* * Check whether c is a one-character inventory label. If so, set *vp * to the corresponding number and return 1; if not, return 0 without * storing through vp. */ static int inv_c_is_1(char c, int *vp) { char *xp; xp = memchr(&icv[0],c,26); if (xp) { *vp = xp - &icv[0]; return(1); } return(0); } /* * Check whether c is the first character of a two-character inventory * label. If so, set *vp to the corresponding base number needed so * that inv_c_is_2_2 will produce the correct index and return 1; if * not, return 0 without storing through vp. */ static int inv_c_is_2_1(char c, int *vp) { char *xp; xp = memchr(&icv[26],c,36); if (xp) { *vp = (xp - &icv[26]) * 62; return(1); } return(0); } /* * Check whether c is the second character of a two-character inventory * label. If so, *vp is assumed to be set as if by inv_c_is_2_1 and * it is updated to the actual inventory index, and 1 is returned; * otherwise, 0 is returned. */ static int inv_c_is_2_2(char c, int *vp) { char *xp; xp = memchr(&icv[0],c,62); if (xp) { *vp += (xp - &icv[0]) + 26; return(1); } return(0); } /* * Find an INVOBJ in an inventory by index. */ static INVOBJ *x_in_inv(INVENT *inv, int x, int (*interest)(INVOBJ *)) { INVOBJ *io; for (io=inv->inv;io;io=io->link) if ((io->xwi == x) && (*interest)(io)) return(io); return(0); } /* * SIKA = Show Inventory Keystroke Action. These are the return values * from show_inventory_'s keystroke function: * * SI_K_IGNORE: * Ignore this keystroke entirely. * SI_K_CONTINUE: * Save this keystroke and accept another. * SI_K_ABORT: * Quit now. (This may be success or may be an error.) */ typedef enum { SI_K_IGNORE = 1, SI_K_CONTINUE, SI_K_ABORT, } SIKA; /* * The internal show-inventory function. This just uses display_list * to page through the inventory, possibly accepting additional * characters, depending on keystroke. inv is the inventory, promptp * points to a prompt string, interest is a filter on the inventory to * decide which items to show, and keystroke is used to process user * input. */ static void show_inventory_(INVENT *inv, const char **promptp, int (*interest)(INVOBJ *), SIKA (*keystroke)(int)) { INVOBJ *io; DLLS line(FILE *f) { while (io && !(*interest)(io)) io = io->link; if (! io) return(DL_L_DONE); format_inv_line(io,f); io = io->link; return(DL_L_MORE); } DLKS key(int ks) { switch ((*keystroke)(ks)) { case SI_K_IGNORE: return(DL_K_IGNORE); break; case SI_K_CONTINUE: return(DL_K_CONTINUE); break; case SI_K_ABORT: return(DL_K_ABORT); break; default: abort(); break; } } io = inv->inv; display_list(&line,promptp,&key); showdisp(); } /* * A show-inventory keystroke action for just looking at inventory, not * selecting an item from it. */ static SIKA si_justmore(int ch) { switch (ch) { case ' ': case '\r': case '\n': return(SI_K_CONTINUE); break; case '\e': return(SI_K_ABORT); break; } return(SI_K_IGNORE); } /* * The exported show-inventory function. */ void show_inventory(INVENT *inv, const char *prompt, int (*interest)(INVOBJ *)) { show_inventory_(inv,&prompt,interest,&si_justmore); } /* * Exported interface: pick an object from a (filtered) inventory. * Returns the INVOBJ, or nil if the operation is aborted. */ INVOBJ *pick_inventory(INVENT *inv, const char *prompt, int (*interest)(INVOBJ *)) { INVOBJ *io; int c; int d; const char *pp; int pl; char *pt; int ks; int c2; JMP j; void setpt(int c) { if (pt == 0) { pl = strlen(prompt); pt = malloc(pl+2); bcopy(prompt,pt,pl); pt[pl+1] = '\0'; } pt[pl] = c; pp = pt; } SIKA key2(int c) { switch (c) { case ' ': case '\r': case '\n': return(SI_K_CONTINUE); break; case '\e': return(SI_K_ABORT); break; } if (c2) { switch (c) { case '\b': case '\177': c2 = 0; pp = prompt; return(SI_K_IGNORE); break; } if (inv_c_is_2_2(c,&ks)) { io = x_in_inv(inv,ks,interest); if (io) return(SI_K_ABORT); } } else { if (inv_c_is_1(c,&ks)) { io = x_in_inv(inv,ks,interest); if (io) return(SI_K_ABORT); } else if (inv_c_is_2_1(c,&ks)) { setpt(c); c2 = 1; } } return(SI_K_IGNORE); } io = 0; pt = 0; c2 = 0; if (setjmp(j.b)) { pop_sigint_throw(); move(LINES-1,0); clrtoeol(); if (pt) free(pt); return(0); } while <"done"> (1) { move(LINES-1,0); addstr(prompt); clrtoeol(); refresh(); push_sigint_throw(&j); c = getch(); pop_sigint_throw(); switch (c) { case '?': pp = prompt; show_inventory_(inv,&pp,interest,key2); if (io) break <"done">; showdisp(); continue; break; case '\e': io = 0; break <"done">; break; case 0x14: /* ^T */ { char prompt[64]; char resp[64]; char c; char lastc; char *cp; int wantunknown; int wantknown; CHOK pcharok(char typed) { return(index(&resp[0],typed)?CHOK_OK:CHOK_BAD); } int sub_interest(INVOBJ *io) { return( ( (wantunknown && invobj_unidentified(io)) || (wantknown && !invobj_unidentified(io)) || !!index(&resp[0],objtypes[io->v[0]->type].sym) ) && (*interest)(io) ); } cp = &resp[0]; lastc = '\0'; for (io=inv->inv;io;io=io->link) { c = objtypes[io->v[0]->type].sym; if (c != lastc) { if ((cp == &resp[0]) || !memchr(&resp[0],c,cp-&resp[0])) *cp++ = c; lastc = c; } } *cp++ = 'u'; *cp++ = 'k'; *cp = '\0'; sprintf(&prompt[0],"What types [%s]? ",&resp[0]); if (prompt_and_read(&prompt[0],&resp[0],sizeof(resp),0,&pcharok) == PR_OK) { wantunknown = !!index(&resp[0],'u'); wantknown = !!index(&resp[0],'k'); pp = prompt; show_inventory_(inv,&pp,&sub_interest,key2); if (io) break <"done">; showdisp(); } } break; default: if (inv_c_is_1(c,&ks)) { io = x_in_inv(inv,ks,interest); if (io) break <"done">; } else if (inv_c_is_2_1(c,&ks)) { addch(c); refresh(); push_sigint_throw(&j); d = getch(); pop_sigint_throw(); switch (d) { case '?': { int subint(INVOBJ *o) { if ((o->xwi < ks+26) || (o->xwi > ks+26+62)) return(0); return((*interest)(o)); } SIKA subkey(int ch) { switch (ch) { case ' ': case '\r': case '\n': return(SI_K_CONTINUE); break; case '\e': return(SI_K_ABORT); break; case '\b': case '\177': return(SI_K_ABORT); break; } if (inv_c_is_2_2(ch,&ks)) { io = x_in_inv(inv,ks,interest); if (io) return(SI_K_ABORT); } return(SI_K_IGNORE); } setpt(c); show_inventory_(inv,&pp,&subint,subkey); if (io) break <"done">; } break; case '\b': case '\177': break; default: if (inv_c_is_2_2(d,&ks)) { io = x_in_inv(inv,ks,interest); if (io) break <"done">; } beep(); break; } } else { beep(); } break; } } move(LINES-1,0); clrtoeol(); if (pt) free(pt); return(io); } /* * Find and return the lowest xwi value not currently in use by the * inventory. This is used for things like adding new objects to the * inventory. */ int find_xwi(INVENT *inv) { static char *map = 0; static int ma = 0; int ml; int i; INVOBJ *io; ml = 0; for (io=inv->inv;io;io=io->link) { if (io->xwi >= ma) map = realloc(map,ma=io->xwi+1); if (io->xwi >= ml) { if (io->xwi > ml) bzero(map+ml,io->xwi-ml); ml = io->xwi + 1; } map[io->xwi] = 1; } for (i=0;idispn <= newn) return(0); nleft = newn; tleft = old->dispn; new = malloc(sizeof(INVOBJ)); new->inv = old->inv; new->xwi = newxwi; new->dispn = old->dispn - newn; new->link = old->link; old->link = new; new->n = old->n; new->v = old->v; old->dispn = newn; if (old->n == 1) { old->v = malloc(sizeof(OBJ *)); old->v[0] = (*objtypes[new->v[0]->type].ops->split)(new->v[0],newn); return(new); } old->v = malloc(old->n*sizeof(OBJ *)); old->n = 0; i = old->n; j = -1; n = 0; while (1) { if (nleft < 1) break; if (j < 0) { if (n > 0) { if (n == o->number) { if (i < new->n-1) new->v[i] = new->v[new->n-1]; new->n --; } else { o = (*objtypes[o->type].ops->split)(o,n); } old->v[old->n++] = o; } i --; if (i < 0) panic("dispn wrong in inventory_split_n"); o = new->v[i]; j = o->number - 1; } else { j --; } if ((tleft <= nleft) || (rnd(tleft) < nleft)) { n ++; nleft --; } tleft --; } return(new); } /* * Split an inventory object, exported version. If newn is positive, * that many will remain in the old object, with the new object taking * the rest; if newn is negative, its absolute value is how many go to * the new object, with the rest remaining in the old. */ INVOBJ *inventory_split_n(INVOBJ *old, int newn, int newxwi) { INVOBJ *new; if (newn < 0) { INVOBJ t; new = inventory_split_n_(old,-newn,newxwi); if (! new) return(0); t = *new; new->dispn = old->dispn; new->n = old->n; new->v = old->v; old->dispn = t.dispn; old->n = t.n; old->v = t.v; } else { new = inventory_split_n_(old,newn,newxwi); } return(new); } /* * Sort a list of INVOBJs based on a comparison function, returning the * new (sorted) list. * * (*cmp)(A,B) must return negative, zero, or positive according as A * must, may or may not, or must not come before B in the sorted list. */ INVOBJ *sort_inv(INVOBJ *inv, int (*cmp)(INVOBJ *, INVOBJ *)) { INVOBJ *l1; INVOBJ **t1; INVOBJ *l2; INVOBJ **t2; INVOBJ **t; INVOBJ *o; if (! inv->link) return(inv); t1 = &l1; t2 = &l2; while (inv) { *t1 = inv; t1 = &inv->link; inv = inv->link; if (! inv) break; *t2 = inv; t2 = &inv->link; inv = inv->link; } *t1 = 0; *t2 = 0; l1 = sort_inv(l1,cmp); l2 = sort_inv(l2,cmp); t = &inv; while (1) { if (l1 && (!l2 || ((*cmp)(l1,l2) < 0))) { o = l1; l1 = l1->link; } else if (l2) { o = l2; l2 = l2->link; } else { break; } *t = o; t = &o->link; } *t = 0; return(inv); } /* * Initialize an INVENT to be of a given type. The type must be * followed by a backpointer: if t is IT_MON, the backpointer must be * a MONST *; if IT_LOC, a LOC *. */ void inv_init(INVENT *i, INVTYPE t, ...) { va_list ap; i->inv = 0; i->type = t; va_start(ap,t); switch (t) { case IT_MON: i->u.m = va_arg(ap,MONST *); break; case IT_LOC: i->u.l = va_arg(ap,LOC *); break; default: panic("bad type %d to inv_init",t); break; } va_end(ap); } /* * Split a plural OBJ. old is the OBJ to split; n is the number to be * split off. Returns a new OBJ holding n of the old OBJ's objects; * the old OBJ holds the remainder. It is the caller's responsibility * to ensure that n is strictly between 0 and the object's number. */ OBJ *std_split(OBJ *old, int n) { OBJ *o; old->number -= n; o = obj_make(old->type); o->number = n; return(o); } /* * Scan an inventory, looking for an object satisfying a predicate. * Returns the first one found, or nil if none of them match. */ INVOBJ *inv_scan(INVENT *inv, int (*interest)(INVOBJ *)) { INVOBJ *io; for (io=inv->inv;io;io=io->link) if ((*interest)(io)) return(io); return(0); } /* * Inventory predicate that looks for unidentified objects. */ int invobj_unidentified(INVOBJ *io) { return(!(*objtypes[io->v[0]->type].ops->identified)(io)); } /* * Show a one-line inventory listing of a single INVOBJ. */ void pline_invobj(INVOBJ *io) { char *s; FILE *f; f = fopenmalloc(&s); format_inv_line(io,f); fclose(f); pline(s); free(s); } /* * Identify an INVOBJ. If noisy is true, report the result. */ void identify_it(INVOBJ *io, int noisy) { io = (*objtypes[io->v[0]->type].ops->identify)(io); if (noisy) pline_invobj(io); } /* * Remove one OBJ from an INVOBJ, the one at index inx. It is the * caller's responsibility to ensure that inx is in range for the * INVOBJ. */ OBJ *remove_obj_from_invobj(int inx, INVOBJ *io) { OBJ *o; o = io->v[inx]; io->n --; if (inx != io->n) io->v[inx] = io->v[io->n]; io->dispn -= o->number; return(o); }