/* * Implementation of scrolls. * * I have tried to factor out you-and/or-here common logic from * identify and charging, but have failed. Some of it can be factored * out, certainly - notably the actual action - but making the * messages read naturally in both cases is difficult if the control * flow is identical. I suspect charging may be more easily * collapsible with other possible you-and/or-here things, though, if * there are any. */ #include #include "obj.h" #include "mon.h" #include "vars.h" #include "dice.h" #include "util.h" #include "pline.h" #include "debug.h" #include "format.h" #include "damage.h" #include "structs.h" #include "scrsyms.h" #include "display.h" #include "obj-map.h" #include "phasing.h" #include "disputil.h" #include "objtypes.h" #include "obj-wand.h" #include "obj-ring.h" #include "mon-@-you.h" #include "obj-weapon.h" #include "obj-armour.h" #include "digdungeon.h" #include "obj-scroll.h" /* * Implementation of the scroll of identify: identify an object in the * player's inventory or the player's location's inventory. * * Note that this takes care to ensure that the player can say to * identify a location object and then, after looking at the * location's inventory, switch to the player inventory. Or vice * versa, of course. */ static void scroll_of_identify(void) { int dohere; int doinv; INVOBJ *io; objtypes[OBJ_SCROLL_OF_IDENTIFY].flags |= OTF_KNOWN; pline("This is a scroll of identify!"); dohere = 0; doinv = 0; if (you->loc->objs.inv) { if (you->invent.inv) { if (inv_scan(&you->invent,&invtest_unidentified)) doinv = 1; if (you->loc->objs.inv->link) { if (inv_scan(&you->loc->objs,&invtest_unidentified)) { dohere = 1; pline("There are objects here"); } else { pline("There are objects here, but they are all identified already"); } } else { if (inv_scan(&you->loc->objs,&invtest_unidentified)) { dohere = 1; pline("There is an object here"); } else { pline("There is an object here, but it is identified already"); } } if (!dohere && !doinv) { pline("What a pity everything in your inventory is also identified!"); } } else { if (you->loc->objs.inv->link) { pline("You are empty-handed, but there are objects here"); if (inv_scan(&you->loc->objs,&invtest_unidentified)) { dohere = 1; } else { pline("What a pity they are all identified already!"); } } else { pline("You are empty-handed, but there is an object here"); if (inv_scan(&you->loc->objs,&invtest_unidentified)) { dohere = 1; } else { pline("What a pity it is identified already!"); } } } } else { if (you->invent.inv) { if (inv_scan(&you->invent,&invtest_unidentified)) { doinv = 1; } else { pline("What a pity everything you are carrying is already identified!"); } } else { pline("What a pity you're empty-handed!"); } } if (dohere || doinv) showmsgbuf(1); else return; if (dohere && doinv) { do { int i; INVENT *inv; i = choose_one("Which location do you want to identify an object from?","here",1,"your inventory",2,(char *)0,-1); if (i < 0) { if (confirm("Don't identify anything?")) { io = 0; break; } continue; } switch (i) { case 1: inv = &you->loc->objs; break; case 2: inv = &you->invent; break; default: panic("impossible inventory list"); } io = pick_inventory(inv,"Identify what (ESC for more choices)?",&invtest_unidentified,0); if (io) break; } while (1); } else { do { io = pick_inventory(dohere?&you->loc->objs:&you->invent,"Identify what?",&invtest_unidentified,0); } while (!io && !confirm("Don't identify anything?")); } if (io) identify_it(io,1); } /* * Implementation of the scroll of magic mapping. This can either * update the map-in-use or it can _become_ the map-in-use. (XXX the * latter does not currently work; the code here is correct, but * map_replace_current() doesn't work.) */ static void scroll_of_magic_mapping(INVOBJ *io) { objtypes[OBJ_SCROLL_OF_MAGIC_MAPPING].flags |= OTF_KNOWN; pline("This is a scroll of magic mapping!"); showmsgbuf(1); if (map && confirm("Do you want to just update the map you're using?")) { inv_remove(io->inv,io); invobjfree(io); map_complete(map,you->loc->on); } else { objfree(io->v[0]); io->v[0] = obj_make(OBJ_MAP); map_complete(io->v[0],you->loc->on); if (confirm("Use this map for this level?")) map_replace_current(io->v[0]); pline_invobj(io); } cleardisp(); drawmapped(you->loc->on); } /* * Implementation of the scroll of phasing. */ static void scroll_of_phasing(void) { objtypes[OBJ_SCROLL_OF_PHASING].flags |= OTF_KNOWN; pline("You are now out of phase!"); phase_mon(you,roll("50d4"),roll("10+2d6")); } /* * Implementation of the scroll of remove curse. Uncurse everything in * the player's inventory. * * This assumes nobody but the player ever reads a scroll of remove * curse. */ static void scroll_of_remove_curse(void) { int uncurse_io(INVOBJ *io) { int i; if (! obj_cursable(io->v[0]->type)) return(0); for (i=io->n-1;i>=0;i--) obj_set_cursed(io->v[i],0); return(0); } objtypes[OBJ_SCROLL_OF_REMOVE_CURSE].flags |= OTF_KNOWN; inv_map(&you->invent,&uncurse_io); pline("You feel a cleansing power sweep over you!"); } /* * Inventory test function for chargeable items (wands). */ static int invtest_chargeable(INVOBJ *io) { return(objtypes[io->v[0]->type].class == OC_WAND); } /* * Inventory test function for enchantable items (armour, weapons, * rings with plusses). In order to avoid leaking information, we * also accept unidentified rings. (Arguably we could/should reject * unidentified rings if all ringtypes that have plusses are known, * but doing that is more expensive than I like here.) */ static int invtest_enchantable(INVOBJ *io) { switch (objtypes[io->v[0]->type].class) { case OC_ARMOUR: case OC_WEAPON: return(1); break; case OC_RING: return( ringtype_has_plusses(io->v[0]->type) || !(objtypes[io->v[0]->type].flags & OTF_KNOWN) ); break; default: return(0); break; } } /* * Implementation of the scroll of charging. */ static void scroll_of_charging(void) { int nhere; int ninv; INVOBJ *io; const char *prefix; objtypes[OBJ_SCROLL_OF_CHARGING].flags |= OTF_KNOWN; pline("This is a scroll of charging!"); ninv = inv_count(&you->invent,&invtest_chargeable); nhere = inv_count(&you->loc->objs,&invtest_chargeable); if (nhere > 1) { pline("There are wands here"); prefix = "Also, you"; } else if (nhere) { pline("There is a wand here"); prefix = "Also, you"; } else if (you->loc->objs.inv) { if (you->loc->objs.inv->link) { pline("There are objects here, but no wands"); } else { pline("There is an object here, but it is not a wand"); } prefix = "But you"; } else { prefix = "You"; } if (ninv > 1) { if (you->loc->objs.inv) pline("%s are carrying wands",prefix); } else if (ninv) { if (you->loc->objs.inv) pline("%s are carrying a wand",prefix); } else if (nhere) { // say nothing about player inventory in this case } else if (you->loc->objs.inv) { pline("How unfortunate you are carrying no wands either!"); } else { pline("How unfortunate you have no wands to charge!"); } if (nhere || ninv) showmsgbuf(1); else return; if (nhere && ninv) { do { int i; INVENT *inv; i = choose_one("Which location do you want to charge a wand from?","here",1,"your inventory",2,(char *)0,-1); if (i < 0) { if (confirm("Don't charge anything?")) { io = 0; break; } continue; } switch (i) { case 1: inv = &you->loc->objs; break; case 2: inv = &you->invent; break; default: panic("impossible inventory list"); } io = pick_inventory(inv,"Charge what (ESC for more choices)?",&invtest_chargeable,0); if (io) break; } while (1); } else { do { io = pick_inventory(nhere?&you->loc->objs:&you->invent,"Charge what?",&invtest_chargeable,0); } while (!io && !confirm("Don't charge anything?")); } if (io && !wand_charge(io)) pline_invobj(io); } /* * Implementation of the scroll of explosion. */ static void scroll_of_explosion(void) { int d; objtypes[OBJ_SCROLL_OF_EXPLOSION].flags |= OTF_KNOWN; d = roll("4d1000+6000"); pline("The scroll explodes for %d damage!",d); (*you->ops->takedamage)(you,damage_simple(d,DK_ORDINARY,0)); } /* * Implementation of the scroll of teleportation. */ static void scroll_of_teleportation(void) { LOC *lc; int count; int ck(LOC *loc) { return(occupiable(loc)||(1>--count)); } objtypes[OBJ_SCROLL_OF_TELEPORTATION].flags |= OTF_KNOWN; pline("This is a scroll of teleportation!"); if (you->effflags & MEF_TELEPORT_CTL) { while (1) { showmsgbuf(1); lc = pick_location("Where do you want to go?"); if (lc == you->loc) break; if (lc) { if (level_plane(you->loc->on) != level_plane(lc->on)) { pline("Can't teleport between planes."); } else if (occupiable(lc) && !lc->monst) { teleport_you(lc); break; } else { pline("Teleport failed - something is in the way."); } } else { pline("You can teleport only to places you know."); } } } else { if (you->loc->on->levelno < 0) { count = 1000; lc = randomloc(you->loc->on,&occupiable); if ((count > 0) || occupiable(lc)) teleport_you(lc); } else { int minlev; int maxlev; LEVEL *lv; int n; int i; minlev = you->loc->on->index - 2; maxlev = you->loc->on->index + 1; if (minlev < 0) minlev = 0; if (maxlev > DUNGEON_LEVELS-1) maxlev = DUNGEON_LEVELS - 1; for (i=20;i>0;i--) { n = rnd(3+maxlev-minlev); if (n < 2) lv = you->loc->on; else lv = &levels[minlev+n-2]; count = 100; lc = randomloc(lv,&occupiable); if ((count > 0) || occupiable(lc)) { teleport_you(lc); break; } } } } } /* * Implementation of the scroll of enchant item. */ static void scroll_of_enchant_item(void) { int nhere; int ninv; INVOBJ *io; const char *prefix; objtypes[OBJ_SCROLL_OF_ENCHANT_ITEM].flags |= OTF_KNOWN; pline("This is a scroll of enchant item!"); ninv = inv_count(&you->invent,&invtest_enchantable); nhere = inv_count(&you->loc->objs,&invtest_enchantable); if (nhere > 1) { pline("There are enchantable items here"); prefix = "Also, you"; } else if (nhere) { pline("There is an enchantable item here"); prefix = "Also, you"; } else if (you->loc->objs.inv) { if (you->loc->objs.inv->link) { pline("There are items here, but none are enchantable"); } else { pline("There is an object here, but it is not enchantable"); } prefix = "But you"; } else { prefix = "You"; } if (ninv > 1) { if (you->loc->objs.inv) pline("%s are carrying enchantable items",prefix); } else if (ninv) { if (you->loc->objs.inv) pline("%s are carrying an enchantable item",prefix); } else if (nhere) { // say nothing about player inventory in this case } else if (you->loc->objs.inv) { pline("How unfortunate you are carrying nothing enchantable either!"); } else { pline("How unfortunate you have nothing enchantable!"); } if (nhere || ninv) showmsgbuf(1); else return; if (nhere && ninv) { do { int i; INVENT *inv; i = choose_one("Which location do you want to enchant an item from?","here",1,"your inventory",2,(char *)0,-1); if (i < 0) { if (confirm("Don't enchant anything?")) { io = 0; break; } continue; } switch (i) { case 1: inv = &you->loc->objs; break; case 2: inv = &you->invent; break; default: panic("impossible inventory list"); } io = pick_inventory(inv,"Enchant what (ESC for more choices)?",&invtest_enchantable,0); if (io) break; } while (1); } else { do { io = pick_inventory(nhere?&you->loc->objs:&you->invent,"Enchant what?",&invtest_enchantable,0); } while (!io && !confirm("Don't enchant anything?")); } if (io) { switch (objtypes[io->v[0]->type].class) { case OC_ARMOUR: armour_enchant(io); break; case OC_WEAPON: weapon_enchant(io); break; case OC_RING: ring_enchant(io); break; } pline_invobj(io); } } /* * The guts of locate-monsters and similar location magic. */ static void locate_something(int (*next)(char *,int *, int *), const char *msg) { unsigned char f[LEV_X][LEV_Y]; char c[LEV_X][LEV_Y]; int x; int y; char ch; int minx; int maxx; int miny; int maxy; int ox; int oy; for (x=LEV_X-1;x>=0;x--) for (y=LEV_Y-1;y>=0;y--) { f[x][y] = 0; c[x][y] = SYM_OOS; } minx = you->loc->x; maxx = minx; miny = you->loc->y; maxy = miny; while ((*next)(&ch,&x,&y)) { if ((ch == SYM_OOS) || (ch == ' ')) continue; c[x][y] = ch; if (x < minx) minx = x; if (x > maxx) maxx = x; if (y < miny) miny = y; if (y > maxy) maxy = y; } ox = dispox; oy = dispoy; if (minx+ox < 0) ox = - minx; if (miny+oy < 0) oy = - miny; if (maxx+ox >= LEV_X) ox = LEV_X - 1 - maxx; if (maxy+oy >= LEV_Y) oy = LEV_Y - 1 - maxy; dispox = ox; dispoy = oy; showmsgbuf(1); clear(); showdisp2(&f[0],&c[0],ox,oy); pline("%s [%d %d]",msg,ox,oy); showmsgbuf(0); move(you->loc->y+oy,you->loc->x+ox); refresh(); tget(); } /* * Implementation of the scroll of locate monsters. */ static void scroll_of_locate_monsters(void) { MONST *m; LEVEL *l; int next(char *cp, int *xp, int *yp) { while (1) { if (! m) return(0); if ((m->loc->on != l) || !monst_cansee_monst(you,m)) { m = m->link; continue; } *cp = m->symbol; *xp = m->loc->x; *yp = m->loc->y; m = m->link; return(1); } } objtypes[OBJ_SCROLL_OF_LOCATE_MONSTERS].flags |= OTF_KNOWN; m = mchain; l = you->loc->on; locate_something(&next,"You sense monsters!"); } /* * Implementation of the scroll of locate objects. */ static void scroll_of_locate_objects(void) { int x; int y; LEVEL *lev; int next(char *cp, int *xp, int *yp) { LOC *l; INVOBJ *io; char ch; int n; void savec(char c) { n ++; if (onein(n)) ch = c; } while (1) { if (y >= LEV_Y) return(0); l = &lev->cells[x][y]; x ++; if (x >= LEV_X) { x = 0; y ++; } if (!l->objs.inv && (!l->monst || !l->monst->invent.inv)) continue; n = 0; for (io=l->objs.inv;io;io=io->link) savec(objtypes[io->v[0]->type].sym); if (l->monst) for (io=l->monst->invent.inv;io;io=io->link) savec(objtypes[io->v[0]->type].sym); if (n < 1) panic("inventories but no objects in scroll_of_locate_objects"); *cp = ch; *xp = l->x; *yp = l->y; return(1); } } objtypes[OBJ_SCROLL_OF_LOCATE_OBJECTS].flags |= OTF_KNOWN; x = 0; y = 0; lev = you->loc->on; locate_something(&next,"You sense objects!"); } /* * Read something. Currently, scrolls are the only readable objects. */ void read_it(INVOBJ *io) { OBJ *o; int autodestroy; if (io->dispn > 1) panic("read_it: >1 object"); o = io->v[0]; if (objtypes[o->type].class != OC_SCROLL) { pline("Reading non-scroll?"); impossible(); return; } autodestroy = 1; switch (o->type) { case OBJ_SCROLL_OF_CHARGING: scroll_of_charging(); break; case OBJ_SCROLL_OF_ENCHANT_ITEM: scroll_of_enchant_item(); break; case OBJ_SCROLL_OF_EXPLOSION: scroll_of_explosion(); break; // case OBJ_SCROLL_OF_HOLD_OTHERS: case OBJ_SCROLL_OF_IDENTIFY: inv_remove(io->inv,io); invobjfree(io); scroll_of_identify(); autodestroy = 0; break; // case OBJ_SCROLL_OF_INVULNERABILITY: // case OBJ_SCROLL_OF_LIGHT: case OBJ_SCROLL_OF_LOCATE_MONSTERS: scroll_of_locate_monsters(); break; case OBJ_SCROLL_OF_LOCATE_OBJECTS: scroll_of_locate_objects(); break; case OBJ_SCROLL_OF_MAGIC_MAPPING: scroll_of_magic_mapping(io); autodestroy = 0; break; case OBJ_SCROLL_OF_PHASING: scroll_of_phasing(); break; case OBJ_SCROLL_OF_REMOVE_CURSE: scroll_of_remove_curse(); break; // case OBJ_SCROLL_OF_SECRET_FINDING: // case OBJ_SCROLL_OF_SUMMON_MONSTER: case OBJ_SCROLL_OF_TELEPORTATION: scroll_of_teleportation(); break; default: objtypes[o->type].flags |= OTF_KNOWN; pline("This scroll type is not yet implemented."); break; } if (autodestroy) { inv_remove(io->inv,io); invobjfree(io); } } /* * The identical method for scrolls. */ static int identical_scroll(OBJ *o1, OBJ *o2) { return(o1->type==o2->type); } /* * The collapsible method for scrolls. */ static int collapsible_scroll(OBJ *o1, OBJ *o2) { return(identical_scroll(o1,o2)); } /* * The identified method for scrolls. * * knowledge_show() knows that scrolls use OTF_KNOWN to indicate * knowledge of the title<->function mapping. */ static int identified_scroll(INVOBJ *io) { return(!!(objtypes[io->v[0]->type].flags&OTF_KNOWN)); } /* * The identify method for scrolls. */ static INVOBJ *identify_scroll(INVOBJ *io) { objtypes[io->v[0]->type].flags |= OTF_KNOWN; return(io); } /* * The OBJOPS vector for scrolls. */ #define new_scroll noop_new #define old_scroll noop_old #define format_scroll std_format #define fmt_cond_scroll panic_fmt_cond #define fmt_spec_scroll panic_fmt_spec #define split_scroll std_split #define moved_scroll noop_moved #define cursable_scroll 0 #define setcursed_scroll setcursed_uncursable OBJOPS objops_scroll = OBJOPS_INIT(scroll);