/* NetHack 3.7 engrave.c $NHDT-Date: 1612055954 2021/01/31 01:19:14 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.102 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2012. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" static int stylus_ok(struct obj *); static boolean u_can_engrave(void); static int engrave(void); static const char *blengr(void); char * random_engraving(char *outbuf) { const char *rumor; /* a random engraving may come from the "rumors" file, or from the "engrave" file (formerly in an array here) */ if (!rn2(4) || !(rumor = getrumor(0, outbuf, TRUE)) || !*rumor) (void) get_rnd_text(ENGRAVEFILE, outbuf, rn2, MD_PAD_RUMORS); wipeout_text(outbuf, (int) (strlen(outbuf) / 4), 0); return outbuf; } /* Partial rubouts for engraving characters. -3. */ static const struct { char wipefrom; const char *wipeto; } rubouts[] = { { 'A', "^" }, { 'B', "Pb[" }, { 'C', "(" }, { 'D', "|)[" }, { 'E', "|FL[_" }, { 'F', "|-" }, { 'G', "C(" }, { 'H', "|-" }, { 'I', "|" }, { 'K', "|<" }, { 'L', "|_" }, { 'M', "|" }, { 'N', "|\\" }, { 'O', "C(" }, { 'P', "F" }, { 'Q', "C(" }, { 'R', "PF" }, { 'T', "|" }, { 'U', "J" }, { 'V', "/\\" }, { 'W', "V/\\" }, { 'Z', "/" }, { 'b', "|" }, { 'd', "c|" }, { 'e', "c" }, { 'g', "c" }, { 'h', "n" }, { 'j', "i" }, { 'k', "|" }, { 'l', "|" }, { 'm', "nr" }, { 'n', "r" }, { 'o', "c" }, { 'q', "c" }, { 'w', "v" }, { 'y', "v" }, { ':', "." }, { ';', ",:" }, { ',', "." }, { '=', "-" }, { '+', "-|" }, { '*', "+" }, { '@', "0" }, { '0', "C(" }, { '1', "|" }, { '6', "o" }, { '7', "/" }, { '8', "3o" } }; /* degrade some of the characters in a string */ void wipeout_text( char *engr, /* engraving text */ int cnt, /* number of chars to degrade */ unsigned seed) /* for semi-controlled randomization */ { char *s; int i, j, nxt, use_rubout; unsigned lth = (unsigned) strlen(engr); if (lth && cnt > 0) { while (cnt--) { /* pick next character */ if (!seed) { /* random */ nxt = rn2((int) lth); use_rubout = rn2(4); } else { /* predictable; caller can reproduce the same sequence by supplying the same arguments later, or a pseudo-random sequence by varying any of them */ nxt = seed % lth; seed *= 31, seed %= (BUFSZ - 1); use_rubout = seed & 3; } s = &engr[nxt]; if (*s == ' ') continue; /* rub out unreadable & small punctuation marks */ if (index("?.,'`-|_", *s)) { *s = ' '; continue; } if (!use_rubout) { i = SIZE(rubouts); } else { for (i = 0; i < SIZE(rubouts); i++) if (*s == rubouts[i].wipefrom) { unsigned ln = (unsigned) strlen(rubouts[i].wipeto); /* * Pick one of the substitutes at random. */ if (!seed) { j = rn2((int) ln); } else { seed *= 31, seed %= (BUFSZ - 1); j = seed % ln; } *s = rubouts[i].wipeto[j]; break; } } /* didn't pick rubout; use '?' for unreadable character */ if (i == SIZE(rubouts)) *s = '?'; } } /* trim trailing spaces */ while (lth && engr[lth - 1] == ' ') engr[--lth] = '\0'; } /* check whether hero can reach something at ground level */ boolean can_reach_floor(boolean check_pit) { struct trap *t; if (u.uswallow || (u.ustuck && !sticks(g.youmonst.data) /* assume that arms are pinned rather than that the hero has been lifted up above the floor [doesn't explain how hero can attack the creature holding him or her; that's life in nethack...] */ && attacktype(u.ustuck->data, AT_HUGS)) || (Levitation && !(Is_airlevel(&u.uz) || Is_waterlevel(&u.uz)))) return FALSE; /* Restricted/unskilled riders can't reach the floor */ if (u.usteed && P_SKILL(P_RIDING) < P_BASIC) return FALSE; if (u.uundetected && ceiling_hider(g.youmonst.data)) return FALSE; if (Flying || g.youmonst.data->msize >= MZ_HUGE) return TRUE; if (check_pit && (t = t_at(u.ux, u.uy)) != 0 && (uteetering_at_seen_pit(t) || uescaped_shaft(t))) return FALSE; return TRUE; } /* give a message after caller has determined that hero can't reach */ void cant_reach_floor(coordxy x, coordxy y, boolean up, boolean check_pit) { You("can't reach the %s.", up ? ceiling(x, y) : (check_pit && can_reach_floor(FALSE)) ? "bottom of the pit" : surface(x, y)); } const char * surface(coordxy x, coordxy y) { struct rm *lev = &levl[x][y]; if (u_at(x, y) && u.uswallow && is_animal(u.ustuck->data)) return "maw"; else if (IS_AIR(lev->typ) && Is_airlevel(&u.uz)) return "air"; else if (is_pool(x, y)) return (Underwater && !Is_waterlevel(&u.uz)) ? "bottom" : hliquid("water"); else if (is_ice(x, y)) return "ice"; else if (is_lava(x, y)) return hliquid("lava"); else if (lev->typ == DRAWBRIDGE_DOWN) return "bridge"; else if (IS_ALTAR(levl[x][y].typ)) return "altar"; else if (IS_GRAVE(levl[x][y].typ)) return "headstone"; else if (IS_FOUNTAIN(levl[x][y].typ)) return "fountain"; else if ((IS_ROOM(lev->typ) && !Is_earthlevel(&u.uz)) || IS_WALL(lev->typ) || IS_DOOR(lev->typ) || lev->typ == SDOOR) return "floor"; else return "ground"; } const char * ceiling(coordxy x, coordxy y) { struct rm *lev = &levl[x][y]; const char *what; /* other room types will no longer exist when we're interested -- * see check_special_room() */ if (*in_rooms(x, y, VAULT)) what = "vault's ceiling"; else if (*in_rooms(x, y, TEMPLE)) what = "temple's ceiling"; else if (*in_rooms(x, y, SHOPBASE)) what = "shop's ceiling"; else if (Is_waterlevel(&u.uz)) /* water plane has no surface; its air bubbles aren't below sky */ what = "water above"; else if (IS_AIR(lev->typ)) what = "sky"; else if (Underwater) what = "water's surface"; else if ((IS_ROOM(lev->typ) && !Is_earthlevel(&u.uz)) || IS_WALL(lev->typ) || IS_DOOR(lev->typ) || lev->typ == SDOOR) what = "ceiling"; else what = "rock cavern"; return what; } struct engr * engr_at(coordxy x, coordxy y) { register struct engr *ep = head_engr; while (ep) { if (x == ep->engr_x && y == ep->engr_y) return ep; ep = ep->nxt_engr; } return (struct engr *) 0; } /* Decide whether a particular string is engraved at a specified * location; a case-insensitive substring match is used. * Ignore headstones, in case the player names herself "Elbereth". * * If strict checking is requested, the word is only considered to be * present if it is intact and is the entire content of the engraving. */ boolean sengr_at(const char *s, coordxy x, coordxy y, boolean strict) { register struct engr *ep = engr_at(x, y); if (ep && ep->engr_type != HEADSTONE && ep->engr_time <= g.moves) { return (strict ? !strcmpi(ep->engr_txt, s) : (strstri(ep->engr_txt, s) != 0)); } return FALSE; } void u_wipe_engr(int cnt) { if (can_reach_floor(TRUE)) wipe_engr_at(u.ux, u.uy, cnt, FALSE); } void wipe_engr_at(coordxy x, coordxy y, xint16 cnt, boolean magical) { register struct engr *ep = engr_at(x, y); /* Headstones are indelible */ if (ep && ep->engr_type != HEADSTONE) { debugpline1("asked to erode %d characters", cnt); if (ep->engr_type != BURN || is_ice(x, y) || (magical && !rn2(2))) { if (ep->engr_type != DUST && ep->engr_type != ENGR_BLOOD) { cnt = rn2(1 + 50 / (cnt + 1)) ? 0 : 1; debugpline1("actually eroding %d characters", cnt); } wipeout_text(ep->engr_txt, (int) cnt, 0); while (ep->engr_txt[0] == ' ') ep->engr_txt++; if (!ep->engr_txt[0]) del_engr(ep); } } } void read_engr_at(coordxy x, coordxy y) { struct engr *ep = engr_at(x, y); int sensed = 0; /* Sensing an engraving does not require sight, * nor does it necessarily imply comprehension (literacy). */ if (ep && ep->engr_txt[0]) { switch (ep->engr_type) { case DUST: if (!Blind) { sensed = 1; pline("%s is written here in the %s.", Something, is_ice(x, y) ? "frost" : "dust"); } break; case ENGRAVE: case HEADSTONE: if (!Blind || can_reach_floor(TRUE)) { sensed = 1; pline("%s is engraved here on the %s.", Something, surface(x, y)); } break; case BURN: if (!Blind || can_reach_floor(TRUE)) { sensed = 1; pline("Some text has been %s into the %s here.", is_ice(x, y) ? "melted" : "burned", surface(x, y)); } break; case MARK: if (!Blind) { sensed = 1; pline("There's some graffiti on the %s here.", surface(x, y)); } break; case ENGR_BLOOD: /* "It's a message! Scrawled in blood!" * "What's it say?" * "It says... `See you next Wednesday.'" -- Thriller */ if (!Blind) { sensed = 1; You_see("a message scrawled in blood here."); } break; default: impossible("%s is written in a very strange way.", Something); sensed = 1; } if (sensed) { char *et, buf[BUFSZ]; int maxelen = (int) (sizeof buf /* sizeof "literal" counts terminating \0 */ - sizeof "You feel the words: \"\"."); if ((int) strlen(ep->engr_txt) > maxelen) { (void) strncpy(buf, ep->engr_txt, maxelen); buf[maxelen] = '\0'; et = buf; } else { et = ep->engr_txt; } You("%s: \"%s\".", (Blind) ? "feel the words" : "read", et); if (g.context.run > 0) nomul(0); } } } void make_engr_at(coordxy x, coordxy y, const char *s, long e_time, xint16 e_type) { struct engr *ep; unsigned smem = Strlen(s) + 1; if ((ep = engr_at(x, y)) != 0) del_engr(ep); ep = newengr(smem); (void) memset((genericptr_t)ep, 0, smem + sizeof(struct engr)); ep->nxt_engr = head_engr; head_engr = ep; ep->engr_x = x; ep->engr_y = y; ep->engr_txt = (char *) (ep + 1); Strcpy(ep->engr_txt, s); /* engraving Elbereth shows wisdom */ if (!g.in_mklev && !strcmp(s, "Elbereth")) exercise(A_WIS, TRUE); ep->engr_time = e_time; ep->engr_type = e_type > 0 ? e_type : rnd(N_ENGRAVE - 1); ep->engr_lth = smem; } /* delete any engraving at location */ void del_engr_at(coordxy x, coordxy y) { struct engr *ep = engr_at(x, y); if (ep) del_engr(ep); } /* * freehand - returns true if player has a free hand */ int freehand(void) { return (!uwep || !welded(uwep) || (!bimanual(uwep) && (!uarms || !uarms->cursed))); } /* getobj callback for an object to engrave with */ static int stylus_ok(struct obj *obj) { if (!obj) return GETOBJ_SUGGEST; /* Potential extension: exclude weapons that don't make any sense (such as * bullwhips) and downplay rings and gems that wouldn't be good to write * with (such as glass and non-gem rings) */ if (obj->oclass == WEAPON_CLASS || obj->oclass == WAND_CLASS || obj->oclass == GEM_CLASS || obj->oclass == RING_CLASS) return GETOBJ_SUGGEST; /* Only markers and towels are recommended tools. */ if (obj->oclass == TOOL_CLASS && (obj->otyp == TOWEL || obj->otyp == MAGIC_MARKER)) return GETOBJ_SUGGEST; return GETOBJ_DOWNPLAY; } /* can hero engrave at all (at their location)? */ static boolean u_can_engrave(void) { if (u.uswallow) { if (is_animal(u.ustuck->data)) { pline("What would you write? \"Jonah was here\"?"); return FALSE; } else if (is_whirly(u.ustuck->data)) { cant_reach_floor(u.ux, u.uy, FALSE, FALSE); return FALSE; } } else if (is_lava(u.ux, u.uy)) { You_cant("write on the %s!", surface(u.ux, u.uy)); return FALSE; } else if (is_pool(u.ux, u.uy) || IS_FOUNTAIN(levl[u.ux][u.uy].typ)) { You_cant("write on the %s!", surface(u.ux, u.uy)); return FALSE; } if (Is_airlevel(&u.uz) || Is_waterlevel(&u.uz) /* in bubble */) { You_cant("write in thin air!"); return FALSE; } else if (!accessible(u.ux, u.uy)) { /* stone, tree, wall, secret corridor, pool, lava, bars */ You_cant("write here."); return FALSE; } if (cantwield(g.youmonst.data)) { You_cant("even hold anything!"); return FALSE; } if (check_capacity((char *) 0)) return FALSE; return TRUE; } /* Mohs' Hardness Scale: * 1 - Talc 6 - Orthoclase * 2 - Gypsum 7 - Quartz * 3 - Calcite 8 - Topaz * 4 - Fluorite 9 - Corundum * 5 - Apatite 10 - Diamond * * Since granite is an igneous rock hardness ~ 7, anything >= 8 should * probably be able to scratch the rock. * Devaluation of less hard gems is not easily possible because obj struct * does not contain individual oc_cost currently. 7/91 * * steel - 5-8.5 (usu. weapon) * diamond - 10 * jade - 5-6 (nephrite) * ruby - 9 (corundum) * turquoise - 5-6 * sapphire - 9 (corundum) * opal - 5-6 * topaz - 8 * glass - ~5.5 * emerald - 7.5-8 (beryl) * dilithium - 4-5?? * aquamarine - 7.5-8 (beryl) * iron - 4-5 * garnet - 7.25 (var. 6.5-8) * fluorite - 4 * agate - 7 (quartz) * brass - 3-4 * amethyst - 7 (quartz) * gold - 2.5-3 * jasper - 7 (quartz) * silver - 2.5-3 * onyx - 7 (quartz) * copper - 2.5-3 * moonstone - 6 (orthoclase) * amber - 2-2.5 */ /* the #engrave command */ int doengrave(void) { boolean dengr = FALSE; /* TRUE if we wipe out the current engraving */ boolean doblind = FALSE; /* TRUE if engraving blinds the player */ boolean doknown = FALSE; /* TRUE if we identify the stylus */ boolean eow = FALSE; /* TRUE if we are overwriting oep */ boolean jello = FALSE; /* TRUE if we are engraving in slime */ boolean ptext = TRUE; /* TRUE if we must prompt for engrave text */ boolean teleengr = FALSE; /* TRUE if we move the old engraving */ boolean zapwand = FALSE; /* TRUE if we remove a wand charge */ xint16 type = DUST; /* Type of engraving made */ xint16 oetype = 0; /* will be set to type of current engraving */ char buf[BUFSZ]; /* Buffer for final/poly engraving text */ char ebuf[BUFSZ]; /* Buffer for initial engraving text */ char fbuf[BUFSZ]; /* Buffer for "your fingers" */ char qbuf[QBUFSZ]; /* Buffer for query text */ char post_engr_text[BUFSZ]; /* Text displayed after engraving prompt */ const char *everb; /* Present tense of engraving type */ const char *eloc; /* Where the engraving is (ie dust/floor/...) */ char *sp; /* Place holder for space count of engr text */ size_t len; /* # of nonspace chars of new engraving text */ struct engr *oep = engr_at(u.ux, u.uy); /* The current engraving */ struct obj *otmp; /* Object selected with which to engrave */ char *writer; g.multi = 0; /* moves consumed */ g.nomovemsg = (char *) 0; /* occupation end message */ buf[0] = (char) 0; ebuf[0] = (char) 0; post_engr_text[0] = (char) 0; if (oep) oetype = oep->engr_type; if (is_demon(g.youmonst.data) || is_vampire(g.youmonst.data)) type = ENGR_BLOOD; /* Can the adventurer engrave at all? */ if (!u_can_engrave()) return ECMD_OK; jello = (u.uswallow && !(is_animal(u.ustuck->data) || is_whirly(u.ustuck->data))); /* One may write with finger, or weapon, or wand, or..., or... * Edited by GAN 10/20/86 so as not to change weapon wielded. */ otmp = getobj("write with", stylus_ok, GETOBJ_PROMPT); if (!otmp) /* otmp == cg.zeroobj if fingers */ return ECMD_CANCEL; if (otmp == &cg.zeroobj) { Strcat(strcpy(fbuf, "your "), body_part(FINGERTIP)); writer = fbuf; } else { writer = yname(otmp); } /* There's no reason you should be able to write with a wand * while both your hands are tied up. */ if (!freehand() && otmp != uwep && !otmp->owornmask) { You("have no free %s to write with!", body_part(HAND)); return ECMD_OK; } if (jello) { You("tickle %s with %s.", mon_nam(u.ustuck), writer); Your("message dissolves..."); return ECMD_OK; } if (otmp->oclass != WAND_CLASS && !can_reach_floor(TRUE)) { cant_reach_floor(u.ux, u.uy, FALSE, TRUE); return ECMD_OK; } if (IS_ALTAR(levl[u.ux][u.uy].typ)) { You("make a motion towards the altar with %s.", writer); altar_wrath(u.ux, u.uy); return ECMD_OK; } if (IS_GRAVE(levl[u.ux][u.uy].typ)) { if (otmp == &cg.zeroobj) { /* using only finger */ You("would only make a small smudge on the %s.", surface(u.ux, u.uy)); return ECMD_OK; } else if (!levl[u.ux][u.uy].disturbed) { You("disturb the undead!"); levl[u.ux][u.uy].disturbed = 1; (void) makemon(&mons[PM_GHOUL], u.ux, u.uy, NO_MM_FLAGS); exercise(A_WIS, FALSE); return ECMD_TIME; } } /* SPFX for items */ switch (otmp->oclass) { default: case AMULET_CLASS: case CHAIN_CLASS: case POTION_CLASS: case COIN_CLASS: break; case RING_CLASS: /* "diamond" rings and others should work */ case GEM_CLASS: /* diamonds & other hard gems should work */ if (objects[otmp->otyp].oc_tough) { type = ENGRAVE; break; } break; case ARMOR_CLASS: if (is_boots(otmp)) { type = DUST; break; } /*FALLTHRU*/ /* Objects too large to engrave with */ case BALL_CLASS: case ROCK_CLASS: You_cant("engrave with such a large object!"); ptext = FALSE; break; /* Objects too silly to engrave with */ case FOOD_CLASS: case SCROLL_CLASS: case SPBOOK_CLASS: pline("%s would get %s.", Yname2(otmp), is_ice(u.ux, u.uy) ? "all frosty" : "too dirty"); ptext = FALSE; break; case RANDOM_CLASS: /* This should mean fingers */ break; /* The charge is removed from the wand before prompting for * the engraving text, because all kinds of setup decisions * and pre-engraving messages are based upon knowing what type * of engraving the wand is going to do. Also, the player * will have potentially seen "You wrest .." message, and * therefore will know they are using a charge. */ case WAND_CLASS: if (zappable(otmp)) { check_unpaid(otmp); if (otmp->cursed && !rn2(WAND_BACKFIRE_CHANCE)) { wand_explode(otmp, 0); return ECMD_TIME; } zapwand = TRUE; if (!can_reach_floor(TRUE)) ptext = FALSE; switch (otmp->otyp) { /* DUST wands */ default: break; /* NODIR wands */ case WAN_LIGHT: case WAN_SECRET_DOOR_DETECTION: case WAN_CREATE_MONSTER: case WAN_WISHING: case WAN_ENLIGHTENMENT: zapnodir(otmp); break; /* IMMEDIATE wands */ /* If wand is "IMMEDIATE", remember to affect the * previous engraving even if turning to dust. */ case WAN_STRIKING: Strcpy(post_engr_text, "The wand unsuccessfully fights your attempt to write!"); break; case WAN_SLOW_MONSTER: if (!Blind) { Sprintf(post_engr_text, "The bugs on the %s slow down!", surface(u.ux, u.uy)); } break; case WAN_SPEED_MONSTER: if (!Blind) { Sprintf(post_engr_text, "The bugs on the %s speed up!", surface(u.ux, u.uy)); } break; case WAN_POLYMORPH: if (oep) { if (!Blind) { type = (xint16) 0; /* random */ (void) random_engraving(buf); } else { /* keep the same type so that feels don't change and only the text is altered, but you won't know anyway because you're a _blind writer_ */ if (oetype) type = oetype; xcrypt(blengr(), buf); } dengr = TRUE; } break; case WAN_NOTHING: case WAN_UNDEAD_TURNING: case WAN_OPENING: case WAN_LOCKING: case WAN_PROBING: break; /* RAY wands */ case WAN_MAGIC_MISSILE: ptext = TRUE; if (!Blind) { Sprintf(post_engr_text, "The %s is riddled by bullet holes!", surface(u.ux, u.uy)); } break; /* can't tell sleep from death - Eric Backus */ case WAN_SLEEP: case WAN_DEATH: if (!Blind) { Sprintf(post_engr_text, "The bugs on the %s stop moving!", surface(u.ux, u.uy)); } break; case WAN_COLD: if (!Blind) Strcpy(post_engr_text, "A few ice cubes drop from the wand."); if (!oep || (oep->engr_type != BURN)) break; /*FALLTHRU*/ case WAN_CANCELLATION: case WAN_MAKE_INVISIBLE: if (oep && oep->engr_type != HEADSTONE) { if (!Blind) pline_The("engraving on the %s vanishes!", surface(u.ux, u.uy)); dengr = TRUE; } break; case WAN_TELEPORTATION: if (oep && oep->engr_type != HEADSTONE) { if (!Blind) pline_The("engraving on the %s vanishes!", surface(u.ux, u.uy)); teleengr = TRUE; } break; /* type = ENGRAVE wands */ case WAN_DIGGING: ptext = TRUE; type = ENGRAVE; if (!objects[otmp->otyp].oc_name_known) { if (Verbose(1, doengrave1)) pline("This %s is a wand of digging!", xname(otmp)); doknown = TRUE; } Strcpy(post_engr_text, (Blind && !Deaf) ? "You hear drilling!" /* Deaf-aware */ : Blind ? "You feel tremors." : IS_GRAVE(levl[u.ux][u.uy].typ) ? "Chips fly out from the headstone." : is_ice(u.ux, u.uy) ? "Ice chips fly up from the ice surface!" : (g.level.locations[u.ux][u.uy].typ == DRAWBRIDGE_DOWN) ? "Splinters fly up from the bridge." : "Gravel flies up from the floor."); break; /* type = BURN wands */ case WAN_FIRE: ptext = TRUE; type = BURN; if (!objects[otmp->otyp].oc_name_known) { if (Verbose(1, doengrave2)) pline("This %s is a wand of fire!", xname(otmp)); doknown = TRUE; } Strcpy(post_engr_text, Blind ? "You feel the wand heat up." : "Flames fly from the wand."); break; case WAN_LIGHTNING: ptext = TRUE; type = BURN; if (!objects[otmp->otyp].oc_name_known) { if (Verbose(1, doengrave3)) pline("This %s is a wand of lightning!", xname(otmp)); doknown = TRUE; } if (!Blind) { Strcpy(post_engr_text, "Lightning arcs from the wand."); doblind = TRUE; } else { Strcpy(post_engr_text, !Deaf ? "You hear crackling!" /* Deaf-aware */ : "Your hair stands up!"); } break; /* type = MARK wands */ /* type = ENGR_BLOOD wands */ } } else { /* end if zappable */ /* failing to wrest one last charge takes time */ ptext = FALSE; /* use "early exit" below, return 1 */ /* give feedback here if we won't be getting the "can't reach floor" message below */ if (can_reach_floor(TRUE)) { /* cancelled wand turns to dust */ if (otmp->spe < 0) zapwand = TRUE; /* empty wand just doesn't write */ else pline_The("wand is too worn out to engrave."); } } break; case WEAPON_CLASS: if (otmp->oartifact == ART_FIRE_BRAND) type = BURN; else if (is_blade(otmp)) { if ((int) otmp->spe > -3) type = ENGRAVE; else pline("%s too dull for engraving.", Yobjnam2(otmp, "are")); } break; case TOOL_CLASS: if (otmp == ublindf) { pline( "That is a bit difficult to engrave with, don't you think?"); return ECMD_OK; } switch (otmp->otyp) { case MAGIC_MARKER: if (otmp->spe <= 0) Your("marker has dried out."); else type = MARK; break; case TOWEL: /* Can't really engrave with a towel */ ptext = FALSE; if (oep) { if (oep->engr_type == DUST || oep->engr_type == ENGR_BLOOD || oep->engr_type == MARK) { if (is_wet_towel(otmp)) dry_a_towel(otmp, -1, TRUE); if (!Blind) You("wipe out the message here."); else pline("%s %s.", Yobjnam2(otmp, "get"), is_ice(u.ux, u.uy) ? "frosty" : "dusty"); dengr = TRUE; } else { pline("%s can't wipe out this engraving.", Yname2(otmp)); } } else { pline("%s %s.", Yobjnam2(otmp, "get"), is_ice(u.ux, u.uy) ? "frosty" : "dusty"); } break; default: break; } break; case VENOM_CLASS: /* this used to be ``if (wizard)'' and fall through to ILLOBJ_CLASS for normal play, but splash of venom isn't "illegal" because it could occur in normal play via wizard mode bones */ pline("Writing a poison pen letter?"); break; case ILLOBJ_CLASS: impossible("You're engraving with an illegal object!"); break; } if (IS_GRAVE(levl[u.ux][u.uy].typ)) { if (type == ENGRAVE || type == 0) { type = HEADSTONE; } else { /* ensures the "cannot wipe out" case */ type = DUST; dengr = FALSE; teleengr = FALSE; buf[0] = '\0'; } } /* * End of implement setup */ /* Identify stylus */ if (doknown) { learnwand(otmp); if (objects[otmp->otyp].oc_name_known) more_experienced(0, 10); } if (teleengr) { rloc_engr(oep); oep = (struct engr *) 0; } if (dengr) { del_engr(oep); oep = (struct engr *) 0; } /* Something has changed the engraving here */ if (*buf) { make_engr_at(u.ux, u.uy, buf, g.moves, type); if (!Blind) pline_The("engraving now reads: \"%s\".", buf); ptext = FALSE; } if (zapwand && (otmp->spe < 0)) { pline("%s %sturns to dust.", The(xname(otmp)), Blind ? "" : "glows violently, then "); if (!IS_GRAVE(levl[u.ux][u.uy].typ)) You( "are not going to get anywhere trying to write in the %s with your dust.", is_ice(u.ux, u.uy) ? "frost" : "dust"); useup(otmp); otmp = 0; /* wand is now gone */ ptext = FALSE; } /* Early exit for some implements. */ if (!ptext) { if (otmp && otmp->oclass == WAND_CLASS && !can_reach_floor(TRUE)) cant_reach_floor(u.ux, u.uy, FALSE, TRUE); return ECMD_TIME; } /* * Special effects should have deleted the current engraving (if * possible) by now. */ if (oep) { char c = 'n'; /* Give player the choice to add to engraving. */ if (type == HEADSTONE) { /* no choice, only append */ c = 'y'; } else if (type == oep->engr_type && (!Blind || oep->engr_type == BURN || oep->engr_type == ENGRAVE)) { c = yn_function("Do you want to add to the current engraving?", ynqchars, 'y'); if (c == 'q') { pline1(Never_mind); return ECMD_OK; } } if (c == 'n' || Blind) { if (oep->engr_type == DUST || oep->engr_type == ENGR_BLOOD || oep->engr_type == MARK) { if (!Blind) { You("wipe out the message that was %s here.", (oep->engr_type == DUST) ? "written in the dust" : (oep->engr_type == ENGR_BLOOD) ? "scrawled in blood" : "written"); del_engr(oep); oep = (struct engr *) 0; } else { /* defer deletion until after we *know* we're engraving */ eow = TRUE; } } else if (type == DUST || type == MARK || type == ENGR_BLOOD) { You("cannot wipe out the message that is %s the %s here.", oep->engr_type == BURN ? (is_ice(u.ux, u.uy) ? "melted into" : "burned into") : "engraved in", surface(u.ux, u.uy)); return ECMD_TIME; } else if (type != oep->engr_type || c == 'n') { if (!Blind || can_reach_floor(TRUE)) You("will overwrite the current message."); eow = TRUE; } } else if (oep && (int) strlen(oep->engr_txt) >= BUFSZ - 1) { There("is no room to add anything else here."); return ECMD_TIME; } } eloc = surface(u.ux, u.uy); switch (type) { default: everb = (oep && !eow ? "add to the weird writing on" : "write strangely on"); break; case DUST: everb = (oep && !eow ? "add to the writing in" : "write in"); eloc = is_ice(u.ux, u.uy) ? "frost" : "dust"; break; case HEADSTONE: everb = (oep && !eow ? "add to the epitaph on" : "engrave on"); break; case ENGRAVE: everb = (oep && !eow ? "add to the engraving in" : "engrave in"); break; case BURN: everb = (oep && !eow ? (is_ice(u.ux, u.uy) ? "add to the text melted into" : "add to the text burned into") : (is_ice(u.ux, u.uy) ? "melt into" : "burn into")); break; case MARK: everb = (oep && !eow ? "add to the graffiti on" : "scribble on"); break; case ENGR_BLOOD: everb = (oep && !eow ? "add to the scrawl on" : "scrawl on"); break; } /* Tell adventurer what is going on */ if (otmp != &cg.zeroobj) You("%s the %s with %s.", everb, eloc, doname(otmp)); else You("%s the %s with your %s.", everb, eloc, body_part(FINGERTIP)); /* Prompt for engraving! */ Sprintf(qbuf, "What do you want to %s the %s here?", everb, eloc); getlin(qbuf, ebuf); /* convert tabs to spaces and condense consecutive spaces to one */ mungspaces(ebuf); /* Count the actual # of chars engraved not including spaces */ len = strlen(ebuf); for (sp = ebuf; *sp; sp++) if (*sp == ' ') len -= 1; if (len == 0 || index(ebuf, '\033')) { if (zapwand) { if (!Blind) pline("%s, then %s.", Tobjnam(otmp, "glow"), otense(otmp, "fade")); return ECMD_TIME; } else { pline1(Never_mind); return ECMD_OK; } } /* A single `x' is the traditional signature of an illiterate person */ if (len != 1 || (!index(ebuf, 'x') && !index(ebuf, 'X'))) if (!u.uconduct.literate++) livelog_printf(LL_CONDUCT, "became literate by engraving \"%s\"", ebuf); /* Mix up engraving if surface or state of mind is unsound. Note: this won't add or remove any spaces. */ for (sp = ebuf; *sp; sp++) { if (*sp == ' ') continue; if (((type == DUST || type == ENGR_BLOOD) && !rn2(25)) || (Blind && !rn2(11)) || (Confusion && !rn2(7)) || (Stunned && !rn2(4)) || (Hallucination && !rn2(2))) *sp = ' ' + rnd(96 - 2); /* ASCII '!' thru '~' (excludes ' ' and DEL) */ } /* Previous engraving is overwritten */ if (eow) { del_engr(oep); oep = (struct engr *) 0; } Strcpy(g.context.engraving.text, ebuf); g.context.engraving.nextc = g.context.engraving.text; g.context.engraving.stylus = otmp; g.context.engraving.type = type; g.context.engraving.pos.x = u.ux; g.context.engraving.pos.y = u.uy; g.context.engraving.actionct = 0; set_occupation(engrave, "engraving", 0); if (post_engr_text[0]) pline("%s", post_engr_text); if (doblind && !resists_blnd(&g.youmonst)) { You("are blinded by the flash!"); make_blinded((long) rnd(50), FALSE); if (!Blind) Your1(vision_clears); } /* Engraving will always take at least one action via being run as an * occupation, so do not count this setup as taking time. */ return ECMD_OK; } /* occupation callback for engraving some text */ static int engrave(void) { struct engr *oep; char buf[BUFSZ]; /* holds the post-this-action engr text, including * anything already there */ const char *finishverb; /* "You finish [foo]." */ struct obj * stylus; /* shorthand for g.context.engraving.stylus */ boolean firsttime = (g.context.engraving.actionct == 0); int rate = 10; /* # characters that can be engraved in this action */ boolean truncate = FALSE; boolean carving = (g.context.engraving.type == ENGRAVE || g.context.engraving.type == HEADSTONE); boolean dulling_wep, marker; char *endc; /* points at character 1 beyond the last character to engrave this action */ int i, space_left; if (g.context.engraving.pos.x != u.ux || g.context.engraving.pos.y != u.uy) { /* teleported? */ pline("You are unable to continue engraving."); return 0; } /* Stylus might have been taken out of inventory and destroyed somehow. * Not safe to dereference stylus until after this. */ if (g.context.engraving.stylus == &cg.zeroobj) { /* bare finger */ stylus = (struct obj *) 0; } else { for (stylus = g.invent; stylus; stylus = stylus->nobj) { if (stylus == g.context.engraving.stylus) { break; } } if (!stylus) { pline("You are unable to continue engraving."); return 0; } } dulling_wep = (carving && stylus && stylus->oclass == WEAPON_CLASS && (stylus->otyp != ATHAME || stylus->cursed)); marker = (stylus && stylus->otyp == MAGIC_MARKER && g.context.engraving.type == MARK); g.context.engraving.actionct++; /* sanity checks */ if (dulling_wep && !is_blade(stylus)) { impossible("carving with non-bladed weapon"); } else if (g.context.engraving.type == MARK && !marker) { impossible("making graffiti with non-marker stylus"); } /* Step 1: Compute rate. */ if (carving && stylus && (dulling_wep || stylus->oclass == RING_CLASS || stylus->oclass == GEM_CLASS)) { /* slow engraving methods */ rate = 1; } else if (marker) { /* one charge / 2 letters */ rate = min(rate, stylus->spe * 2); } /* Step 2: Compute last character that can be engraved this action. */ i = rate; for (endc = g.context.engraving.nextc; *endc && i > 0; endc++) { if (*endc != ' ') { i--; } } /* Step 3: affect stylus from engraving - it might wear out. */ if (dulling_wep) { /* Dull the weapon at a rate of -1 enchantment per 2 characters, * rounding down. * The number of characters obtainable given starting enchantment: * -2 => 3, -1 => 5, 0 => 7, +1 => 9, +2 => 11 * Note: this does not allow a +0 anything (except an athame) to * engrave "Elbereth" all at once. * However, you can engrave "Elb", then "ere", then "th", by taking * advantage of the rounding down. */ if (firsttime) { pline("%s dull.", Yobjnam2(stylus, "get")); } if (g.context.engraving.actionct % 2 == 1) { /* 1st, 3rd, ... action */ /* deduct a point on 1st, 3rd, 5th, ... turns, unless this is the * last character being engraved (a rather convoluted way to round * down), but always deduct a point on the 1st turn to prevent * zero-cost engravings. * Check for truncation *before* deducting a point - otherwise, * attempting to e.g. engrave 3 characters with a -2 weapon will * stop at the 1st. */ if (stylus->spe <= -3) { if (firsttime) { impossible("<= -3 weapon valid for engraving"); } truncate = TRUE; } else if (*endc || g.context.engraving.actionct == 1) { stylus->spe -= 1; update_inventory(); } } } else if (marker) { int ink_cost = max(rate / 2, 1); /* Prevent infinite graffiti */ if (stylus->spe < ink_cost) { impossible("overly dry marker valid for graffiti?"); ink_cost = stylus->spe; truncate = TRUE; } stylus->spe -= ink_cost; update_inventory(); if (stylus->spe == 0) { /* can't engrave any further; truncate the string */ Your("marker dries out."); truncate = TRUE; } } switch (g.context.engraving.type) { default: finishverb = "your weird engraving"; break; case DUST: finishverb = "writing in the dust"; break; case HEADSTONE: case ENGRAVE: finishverb = "engraving"; break; case BURN: finishverb = is_ice(u.ux, u.uy) ? "melting your message into the ice" : "burning your message into the floor"; break; case MARK: finishverb = "defacing the dungeon"; break; case ENGR_BLOOD: finishverb = "scrawling"; } /* actions that happen at the end of every engraving action go here */ buf[0] = '\0'; oep = engr_at(u.ux, u.uy); if (oep) /* add to existing engraving */ Strcpy(buf, oep->engr_txt); space_left = (int) (sizeof buf - strlen(buf) - 1U); if (endc - g.context.engraving.nextc > space_left) { You("run out of room to write."); endc = g.context.engraving.nextc + space_left; truncate = TRUE; } /* If the stylus did wear out mid-engraving, truncate the input so that we * can't go any further. */ if (truncate && *endc != '\0') { *endc = '\0'; You("are only able to write \"%s\".", g.context.engraving.text); } else { /* input was not truncated; stylus may still have worn out on the last * character, though */ truncate = FALSE; } (void) strncat(buf, g.context.engraving.nextc, min(space_left, endc - g.context.engraving.nextc)); make_engr_at(u.ux, u.uy, buf, g.moves - g.multi, g.context.engraving.type); if (*endc) { g.context.engraving.nextc = endc; return 1; /* not yet finished this turn */ } else { /* finished engraving */ /* actions that happen after the engraving is finished go here */ if (truncate) { /* Now that "You are only able to write 'foo'" also prints at the * end of engraving, this might be redundant. */ You("cannot write any more."); } else if (!firsttime) { /* only print this if engraving took multiple actions */ You("finish %s.", finishverb); } g.context.engraving.text[0] = '\0'; g.context.engraving.nextc = (char *) 0; g.context.engraving.stylus = (struct obj *) 0; } return 0; } /* while loading bones, clean up text which might accidentally or maliciously disrupt player's terminal when displayed */ void sanitize_engravings(void) { struct engr *ep; for (ep = head_engr; ep; ep = ep->nxt_engr) { sanitize_name(ep->engr_txt); } } void save_engravings(NHFILE *nhfp) { struct engr *ep, *ep2; unsigned no_more_engr = 0; for (ep = head_engr; ep; ep = ep2) { ep2 = ep->nxt_engr; if (ep->engr_lth && ep->engr_txt[0] && perform_bwrite(nhfp)) { if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t)&(ep->engr_lth), sizeof ep->engr_lth); bwrite(nhfp->fd, (genericptr_t)ep, sizeof (struct engr) + ep->engr_lth); } } if (release_data(nhfp)) dealloc_engr(ep); } if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t)&no_more_engr, sizeof no_more_engr); } if (release_data(nhfp)) head_engr = 0; } void rest_engravings(NHFILE *nhfp) { struct engr *ep; unsigned lth = 0; head_engr = 0; while (1) { if (nhfp->structlevel) mread(nhfp->fd, (genericptr_t) <h, sizeof(unsigned)); if (lth == 0) return; ep = newengr(lth); if (nhfp->structlevel) { mread(nhfp->fd, (genericptr_t) ep, sizeof(struct engr) + lth); } ep->nxt_engr = head_engr; head_engr = ep; ep->engr_txt = (char *) (ep + 1); /* Andreas Bormann */ /* mark as finished for bones levels -- no problem for * normal levels as the player must have finished engraving * to be able to move again */ ep->engr_time = g.moves; } } DISABLE_WARNING_FORMAT_NONLITERAL /* to support '#stats' wizard-mode command */ void engr_stats(const char *hdrfmt, char *hdrbuf, long *count, long *size) { struct engr *ep; Sprintf(hdrbuf, hdrfmt, (long) sizeof (struct engr)); *count = *size = 0L; for (ep = head_engr; ep; ep = ep->nxt_engr) { ++*count; *size += (long) sizeof *ep + (long) ep->engr_lth; } } RESTORE_WARNING_FORMAT_NONLITERAL void del_engr(struct engr *ep) { if (ep == head_engr) { head_engr = ep->nxt_engr; } else { struct engr *ept; for (ept = head_engr; ept; ept = ept->nxt_engr) if (ept->nxt_engr == ep) { ept->nxt_engr = ep->nxt_engr; break; } if (!ept) { impossible("Error in del_engr?"); return; } } dealloc_engr(ep); } /* randomly relocate an engraving */ void rloc_engr(struct engr *ep) { int tx, ty, tryct = 200; do { if (--tryct < 0) return; tx = rn1(COLNO - 3, 2); ty = rn2(ROWNO); } while (engr_at(tx, ty) || !goodpos(tx, ty, (struct monst *) 0, 0)); ep->engr_x = tx; ep->engr_y = ty; } /* Create a headstone at the given location. * The caller is responsible for newsym(x, y). */ void make_grave(coordxy x, coordxy y, const char *str) { char buf[BUFSZ]; /* Can we put a grave here? */ if ((levl[x][y].typ != ROOM && levl[x][y].typ != GRAVE) || t_at(x, y)) return; /* Make the grave */ if (!set_levltyp(x, y, GRAVE)) return; /* Engrave the headstone */ del_engr_at(x, y); if (!str) str = get_rnd_text(EPITAPHFILE, buf, rn2, MD_PAD_RUMORS); make_engr_at(x, y, str, 0L, HEADSTONE); return; } static const char blind_writing[][21] = { {0x44, 0x66, 0x6d, 0x69, 0x62, 0x65, 0x22, 0x45, 0x7b, 0x71, 0x65, 0x6d, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, {0x51, 0x67, 0x60, 0x7a, 0x7f, 0x21, 0x40, 0x71, 0x6b, 0x71, 0x6f, 0x67, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x49, 0x6d, 0x73, 0x69, 0x62, 0x65, 0x22, 0x4c, 0x61, 0x7c, 0x6d, 0x67, 0x24, 0x42, 0x7f, 0x69, 0x6c, 0x77, 0x67, 0x7e, 0x00}, {0x4b, 0x6d, 0x6c, 0x66, 0x30, 0x4c, 0x6b, 0x68, 0x7c, 0x7f, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x51, 0x67, 0x70, 0x7a, 0x7f, 0x6f, 0x67, 0x68, 0x64, 0x71, 0x21, 0x4f, 0x6b, 0x6d, 0x7e, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x4c, 0x63, 0x76, 0x61, 0x71, 0x21, 0x48, 0x6b, 0x7b, 0x75, 0x67, 0x63, 0x24, 0x45, 0x65, 0x6b, 0x6b, 0x65, 0x00, 0x00, 0x00}, {0x4c, 0x67, 0x68, 0x6b, 0x78, 0x68, 0x6d, 0x76, 0x7a, 0x75, 0x21, 0x4f, 0x71, 0x7a, 0x75, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x00}, {0x44, 0x66, 0x6d, 0x7c, 0x78, 0x21, 0x50, 0x65, 0x66, 0x65, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x44, 0x66, 0x73, 0x69, 0x62, 0x65, 0x22, 0x56, 0x7d, 0x63, 0x69, 0x76, 0x6b, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, }; static const char * blengr(void) { return blind_writing[rn2(SIZE(blind_writing))]; } /*engrave.c*/