diff -r -w -u orig/nethack-3.4.3/include/extern.h nethack-3.4.3/include/extern.h --- orig/nethack-3.4.3/include/extern.h 2003-12-07 23:39:13.000000000 +0000 +++ nethack-3.4.3/include/extern.h 2014-09-23 11:01:42.098895514 +0100 @@ -574,6 +574,8 @@ E void FDECL(del_engr_at, (int,int)); E int NDECL(freehand); E int NDECL(doengrave); +E void FDECL(stylus_disappears, (struct obj *)); /* AIS 23 Sep 2014 */ +E void FDECL(stylus_substitution, (struct obj *,struct obj *)); /* ditto */ E void FDECL(save_engravings, (int,int)); E void FDECL(rest_engravings, (int)); E void FDECL(del_engr, (struct engr *)); diff -r -w -u orig/nethack-3.4.3/src/do_name.c nethack-3.4.3/src/do_name.c --- orig/nethack-3.4.3/src/do_name.c 2003-12-07 23:39:13.000000000 +0000 +++ nethack-3.4.3/src/do_name.c 2014-09-23 21:47:19.708404255 +0100 @@ -392,6 +392,13 @@ else if (obj->oclass == SPBOOK_CLASS) book_substitution(obj, otmp); /* read spellbook */ + /* AIS (23 Sep 2014): Engraving isn't resumable, but we do + this check anyway because it's cheap (nowadays) and hedges + against future changes to the code (e.g. if we ever + implement code where dulling a weapon rubs the name + off). */ + stylus_substitution(obj, otmp); /* engrave */ + /* obfree(obj, otmp); now unnecessary: no pointers on bill */ dealloc_obj(obj); /* let us hope nobody else saved a pointer */ return otmp; diff -r -w -u orig/nethack-3.4.3/src/engrave.c nethack-3.4.3/src/engrave.c --- orig/nethack-3.4.3/src/engrave.c 2003-12-07 23:39:13.000000000 +0000 +++ nethack-3.4.3/src/engrave.c 2014-09-23 21:48:49.984860523 +0100 @@ -4,7 +4,16 @@ #include "hack.h" #include "lev.h" -#include + +/* AIS (23 Sep 2014): reduce code duplication */ +#define STYLUS_ERODES(otmp) ((otmp)->oclass == WEAPON_CLASS && \ + ((otmp)->otyp != ATHAME || (otmp)->cursed)) +/* AIS (23 Sep 2014): 3.4.3 calls isspace here. This could lead to + Trouble if locales get involved in the future (e.g. NAO's detection + code for UTF-8 support); a malicious locale file could allow for + unlimited Elbereths in one turn, via causing all the characters in + the word to count as spaces. */ +#define ENGR_ISSPACE(c) ((c) == ' ' || (c) == '\t') STATIC_VAR NEARDATA struct engr *head_engr; @@ -405,6 +414,267 @@ { ALL_CLASSES, ALLOW_NONE, TOOL_CLASS, WEAPON_CLASS, WAND_CLASS, GEM_CLASS, RING_CLASS, 0 }; +/* Handle engraving over time (AIS, 23 Sep 2014) + + We store where and what the player is engraving as coordinates, in + order to handle the situation where the player is teleported during + the engrave; the z coordinate (current level) is also stored in + case someone forgets a nomul upon an involuntary level change + (e.g. falling off a steed into a hole, which can happen during a + monster turn). (A better approach that avoids static variables + would be to use Nethack4's reset_occupations to cancel the engrave + upon changing location, and its argument cache to avoid having to + store the text to engrave and the object to engrave with. But that + would be a much larger change, so I'm using the statics to keep the + code easier to port.) + + Note that this code is incorrect if it's possible for the player to + start levitating in the middle of an engraving. + + There are two new functions here: one_turn_of_engraving which does + all the actual work of engraving (and whose content was mostly + split from doengrave), and one_more_turn_of_engraving which is just + a wrapper used as an occupation callback. These should only be used + if the caller has already checked that engraving is possible and + performed any effects that happen once per engrave (rather than + once per engrave turn), notably messages. + + There are also two reused functions here: stylus_substitution and + stylus_disappears are heavily based on book_substitution and + book_disappears, copied from src/spell.c. This is needed in case + the stylus is renamed or destroyed during engraving. */ +static int uengrx, uengry; +static d_level uengrz; +static NEARDATA char uengrtxt[BUFSZ]; /* full text of new engraving */ +static struct obj *stylus; /* NULL for fingers, or an object */ + +void +stylus_disappears(obj) +struct obj *obj; +{ + if (obj == stylus) { + stylus = (struct obj *)0; + /* Invalidate any current engraving attempt via using illegal + coordinates (to prevent wand-in-dust engravings becoming + finger-in-dust engravings halfway through). Set x = COLNO+1 + (two cells out of range); y = 0 would be possible but 0 + makes the worst sentinel ever. */ + uengrx = COLNO + 1; + } +} + +/* paranoia, unnecessary in current code, might become relevant in the + future */ +void +stylus_substitution(old_obj, new_obj) +struct obj *old_obj, *new_obj; +{ + if (old_obj == stylus) stylus = new_obj; +} + +/* Returns: 2 if still ongoing; 1 if time was taken but is now complete; + 0 if nothing to do */ +STATIC_PTR int +one_turn_of_engraving(type, first_turn) + xchar type; + boolean first_turn; +{ + int letters = 0; /* length of engraved text, not counting spaces */ + int letters_per_turn; + int letters_per_charge = 0; /* 0 is used for infinity, here */ + int minimum_charges = 0; + const char *finished_msg = NULL; + int uengrtxt_len; + struct engr *existing_engr; + int existing_engr_len = 0; + int newportion_len = 0; + char *newportion, *npp; + char np_temp; + + /* let's not segfault if this happens */ + if (type != DUST && type != ENGR_BLOOD && !stylus) { + impossible("engraving without stylus?"); + return 0; + } + + if (u.ux != uengrx || u.uy != uengry || + u.uz.dnum != uengrz.dnum || u.uz.dlevel != uengrz.dlevel) { + /* player teleported, or stylus was destroyed */ + pline("You can no longer continue engraving."); + return 0; + } + + /* Calculate how many more letters we need to write. */ + uengrtxt_len = strlen(uengrtxt); + existing_engr = engr_at(uengrx, uengry); + if (existing_engr) + existing_engr_len = strlen(existing_engr->engr_txt); + if (uengrtxt_len < existing_engr_len) { + /* should be impossible, have a specific check for this as + it'd otherwise make us read uninitialised memory and the + bug would be very hard to track down */ + impossible("Writing more characters on an engraving makes it shorter?"); + return 0; + } + newportion = uengrtxt + existing_engr_len; + for (npp = newportion; *npp; ++npp) { + if (!ENGR_ISSPACE(*npp)) + letters++; + } + + switch(type) + { + case DUST: + letters_per_turn = 10; + finished_msg = "You finish writing in the dust."; + break; + case HEADSTONE: + case ENGRAVE: + letters_per_turn = 10; + if (STYLUS_ERODES(stylus)) { + /* can write two letters per level of spe (three on the first + turn of engraving), down to a spe of -3; thus a +0 weapon + lets you write "Elberet" */ + letters_per_charge = 2; + minimum_charges = -3; + } else if (stylus->oclass == RING_CLASS || + stylus->oclass == GEM_CLASS) { + /* infinite, but much slower */ + letters_per_turn = 1; + } + finished_msg = "You finish engraving."; + break; + case BURN: + letters_per_turn = 10; + finished_msg = is_ice(uengrx, uengry) ? + "You finish melting your message into the ice.": + "You finish burning your message into the floor."; + break; + case MARK: + letters_per_turn = 10; + if (stylus->otyp == MAGIC_MARKER) { + letters_per_charge = 2; + } + finished_msg = "You finish defacing the dungeon."; + break; + case ENGR_BLOOD: + letters_per_turn = 10; + finished_msg = "You finish scrawling."; + break; + default: + impossible("invalid engraving type %d", (int)type); + return 0; + } + + if (!letters) { + /* Nothing to write. Seems like we finished earlier. */ + if (first_turn) + pline("Do you suffer from writer's block?"); + else + pline("%s", finished_msg); + return 0; + } + + /* The engraving code traditionally rounds /up/. Preserve that + behaviour here, via allowing larger engravings on the first + turn. */ + if (first_turn) { + letters_per_turn = (letters_per_turn * 2) - 1; + } + + /* We can't write more letters than are actually in the string to + write. */ + if (letters_per_turn > letters) + letters_per_turn = letters; + + /* Do we have enough charges left to engrave as much as we want to? */ + if (letters_per_charge) { + int remaining_charges = (int)stylus->spe - minimum_charges; + int charges_spent = letters_per_turn / letters_per_charge; + + if (charges_spent < 1) + charges_spent = 1; + + /* Maybe we can't write any letters at all... */ + if (remaining_charges <= 0) { + if (!first_turn) + pline("You cannot write any more."); + else if (stylus->oclass == WEAPON_CLASS) + pline("Your %s too dull to engrave with.", + aobjnam(stylus, "are")); + else if (stylus->otyp == MAGIC_MARKER) + pline("Your marker has dried out."); + return 0; + } + + /* ...or perhaps we can't write enough letters, in which case + we write only as many as possible this turn, and stop next + turn. */ + if (remaining_charges < charges_spent) { + /* Note: charges spent rounds down; this formula gives the + highest possible result for an undivision, which is not + the same as a multiplication due to rounding */ + letters_per_turn = letters_per_charge * + (remaining_charges + 1) - 1; + charges_spent = remaining_charges; + } + + /* At this point, we're going to go ahead with the engrave. + Drain the charges we're spending. */ + stylus->spe -= charges_spent; + if (stylus->oclass == WEAPON_CLASS && first_turn) + pline("Your %s dull.", aobjnam(stylus, "get")); + } + + /* Work out the string to engrave. We temporarily mutate uengrtxt + in place (via newportion). */ + newportion = uengrtxt + existing_engr_len; + for (npp = newportion; *npp && letters_per_turn; ++npp) { + if (!ENGR_ISSPACE(*npp)) + letters_per_turn--; + } + np_temp = *npp; + *npp = '\0'; + + /* Update the engraving. */ + make_engr_at(uengrx, uengry, uengrtxt, moves, type); + + *npp = np_temp; + + /* Have we finished engraving? The answer is yes if there's nothing but + whitespace from npp to the end of the string. */ + for (; *npp; ++npp) { + if (!ENGR_ISSPACE(*npp)) + return 2; + } + + /* 3.4.3 doesn't print finished_msg if we finish on the first + turn. I'm dubious about this behaviour, but have nonetheless + preserved it. */ + if (!first_turn) + pline("%s", finished_msg); + + return 1; +} + +STATIC_PTR int +one_more_turn_of_engraving() +{ + struct engr *engr; + int timetaken = 0; + + /* Determine the type of engraving from the engraving on the square that + the player's engraving on. */ + engr = engr_at(uengrx, uengry); + + if (engr) + timetaken = one_turn_of_engraving(engr->engr_type, FALSE); + else + pline("You can no longer continue engraving."); + + return timetaken == 2; +} + /* Mohs' Hardness Scale: * 1 - Talc 6 - Orthoclase * 2 - Gypsum 7 - Quartz @@ -452,20 +722,16 @@ 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 */ - int len; /* # of nonspace chars of new engraving text */ - int maxelen; /* Max allowable length of engraving text */ + int len; /* Length, not counting spaces */ + int timetaken; struct engr *oep = engr_at(u.ux,u.uy); /* The current engraving */ struct obj *otmp; /* Object selected with which to engrave */ char *writer; - multi = 0; /* moves consumed */ - nomovemsg = (char *)0; /* occupation end message */ - buf[0] = (char)0; ebuf[0] = (char)0; post_engr_text[0] = (char)0; - maxelen = BUFSZ - 1; if (is_demon(youmonst.data) || youmonst.data->mlet == S_VAMPIRE) type = ENGR_BLOOD; @@ -995,7 +1261,7 @@ /* 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 (isspace(*sp)) continue; + if (ENGR_ISSPACE(*sp)) continue; if (((type == DUST || type == ENGR_BLOOD) && !rn2(25)) || (Blind && !rn2(11)) || (Confusion && !rn2(7)) || (Stunned && !rn2(4)) || (Hallucination && !rn2(2))) @@ -1009,32 +1275,15 @@ oep = (struct engr *)0; } - /* Figure out how long it took to engrave, and if player has - * engraved too much. - */ - switch(type){ - default: - multi = -(len/10); - if (multi) nomovemsg = "You finish your weird engraving."; - break; - case DUST: - multi = -(len/10); - if (multi) nomovemsg = "You finish writing in the dust."; - break; - case HEADSTONE: - case ENGRAVE: - multi = -(len/10); - if ((otmp->oclass == WEAPON_CLASS) && - ((otmp->otyp != ATHAME) || otmp->cursed)) { - multi = -len; - maxelen = ((otmp->spe + 3) * 2) + 1; - /* -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 could now engrave "Elb", then - * "ere", then "th". - */ - Your("%s dull.", aobjnam(otmp, "get")); + /* AIS (23 Sep 2014): much of the rest of this function has + been moved into one_turn_of_engraving */ + + /* Weapons dull when you use them to + engrave, and wands lose charges. This happens over time, so + is handled in one_turn_of_engraving. However, handling + unpaid, etc., only needs to be done once. We've already + done an unpaid check on wands, but not on weapons. */ + if (otmp != &zeroobj && STYLUS_ERODES(otmp) && otmp->unpaid) { if (otmp->unpaid) { struct monst *shkp = shop_keeper(*u.ushops); if (shkp) { @@ -1042,65 +1291,32 @@ bill_dummy_object(otmp); } } - if (len > maxelen) { - multi = -maxelen; - otmp->spe = -3; - } else if (len > 1) - otmp->spe -= len >> 1; - else otmp->spe -= 1; /* Prevent infinite engraving */ - } else - if ( (otmp->oclass == RING_CLASS) || - (otmp->oclass == GEM_CLASS) ) - multi = -len; - if (multi) nomovemsg = "You finish engraving."; - break; - case BURN: - multi = -(len/10); - if (multi) - nomovemsg = is_ice(u.ux,u.uy) ? - "You finish melting your message into the ice.": - "You finish burning your message into the floor."; - break; - case MARK: - multi = -(len/10); - if ((otmp->oclass == TOOL_CLASS) && - (otmp->otyp == MAGIC_MARKER)) { - maxelen = (otmp->spe) * 2; /* one charge / 2 letters */ - if (len > maxelen) { - Your("marker dries out."); - otmp->spe = 0; - multi = -(maxelen/10); - } else - if (len > 1) otmp->spe -= len >> 1; - else otmp->spe -= 1; /* Prevent infinite grafitti */ - } - if (multi) nomovemsg = "You finish defacing the dungeon."; - break; - case ENGR_BLOOD: - multi = -(len/10); - if (multi) nomovemsg = "You finish scrawling."; - break; - } - - /* Chop engraving down to size if necessary */ - if (len > maxelen) { - for (sp = ebuf; (maxelen && *sp); sp++) - if (!isspace(*sp)) maxelen--; - if (!maxelen && *sp) { - *sp = (char)0; - if (multi) nomovemsg = "You cannot write any more."; - You("only are able to write \"%s\"", ebuf); - } } - /* Add to existing engraving */ + /* Add to existing engraving? */ if (oep) Strcpy(buf, oep->engr_txt); (void) strncat(buf, ebuf, (BUFSZ - (int)strlen(buf) - 1)); - make_engr_at(u.ux, u.uy, buf, (moves - multi), type); + Strcpy(uengrtxt, buf); + uengrx = u.ux; + uengry = u.uy; + assign_level(&uengrz, &u.uz); + if (otmp == &zeroobj) + stylus = NULL; + else + stylus = otmp; + + timetaken = one_turn_of_engraving(type, TRUE); - if (post_engr_text[0]) pline(post_engr_text); + if (timetaken == 0) { + /* The engraving was cancelled before anything was + written, e.g. a weapon that was too dull, or a dry + marker */ + return 0; + } + + if (post_engr_text[0]) pline("%s", post_engr_text); if (doblind && !resists_blnd(&youmonst)) { You("are blinded by the flash!"); @@ -1108,7 +1324,13 @@ if (!Blind) Your(vision_clears); } - return(1); + if (timetaken == 2) { + /* We need to set an occupation callback for the other + turns of engraving. */ + set_occupation(one_more_turn_of_engraving, "engraving", 0); + } + + return 1; } void diff -r -w -u orig/nethack-3.4.3/src/save.c nethack-3.4.3/src/save.c --- orig/nethack-3.4.3/src/save.c 2003-12-07 23:39:13.000000000 +0000 +++ nethack-3.4.3/src/save.c 2014-09-23 21:50:25.781344690 +0100 @@ -856,6 +856,7 @@ if (release_data(mode)) { if (otmp->oclass == FOOD_CLASS) food_disappears(otmp); if (otmp->oclass == SPBOOK_CLASS) book_disappears(otmp); + stylus_disappears(otmp); /* AIS 23 Sep 2014 */ otmp->where = OBJ_FREE; /* set to free so dealloc will work */ otmp->timed = 0; /* not timed any more */ otmp->lamplit = 0; /* caller handled lights */ diff -r -w -u orig/nethack-3.4.3/src/shk.c nethack-3.4.3/src/shk.c --- orig/nethack-3.4.3/src/shk.c 2003-12-07 23:39:13.000000000 +0000 +++ nethack-3.4.3/src/shk.c 2014-09-23 21:42:05.998818729 +0100 @@ -788,6 +788,7 @@ if (obj->otyp == LEASH && obj->leashmon) o_unleash(obj); if (obj->oclass == FOOD_CLASS) food_disappears(obj); if (obj->oclass == SPBOOK_CLASS) book_disappears(obj); + stylus_disappears(obj); /* AIS 23 Sep 2014 */ if (Has_contents(obj)) delete_contents(obj); shkp = 0;