The character doesn't know that an unlabeled scroll is blank until they look at the inside of the scroll, but that could be done either by reading or by writing.
421 lines
15 KiB
C
421 lines
15 KiB
C
/* NetHack 3.7 write.c $NHDT-Date: 1702023275 2023/12/08 08:14:35 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.41 $ */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
|
|
staticfn int cost(struct obj *) NONNULLARG1;
|
|
staticfn int write_ok(struct obj *) NO_NNARGS;
|
|
staticfn char *new_book_description(int, char *) NONNULL NONNULLPTRS;
|
|
|
|
/*
|
|
* returns basecost of a scroll or a spellbook
|
|
*/
|
|
staticfn int
|
|
cost(struct obj *otmp)
|
|
{
|
|
if (otmp->oclass == SPBOOK_CLASS)
|
|
return (10 * objects[otmp->otyp].oc_level);
|
|
|
|
switch (otmp->otyp) {
|
|
#ifdef MAIL_STRUCTURES
|
|
case SCR_MAIL:
|
|
return 2;
|
|
#endif
|
|
case SCR_LIGHT:
|
|
case SCR_GOLD_DETECTION:
|
|
case SCR_FOOD_DETECTION:
|
|
case SCR_MAGIC_MAPPING:
|
|
case SCR_AMNESIA:
|
|
case SCR_FIRE:
|
|
case SCR_EARTH:
|
|
return 8;
|
|
case SCR_DESTROY_ARMOR:
|
|
case SCR_CREATE_MONSTER:
|
|
case SCR_PUNISHMENT:
|
|
return 10;
|
|
case SCR_CONFUSE_MONSTER:
|
|
return 12;
|
|
case SCR_IDENTIFY:
|
|
return 14;
|
|
case SCR_ENCHANT_ARMOR:
|
|
case SCR_REMOVE_CURSE:
|
|
case SCR_ENCHANT_WEAPON:
|
|
case SCR_CHARGING:
|
|
return 16;
|
|
case SCR_SCARE_MONSTER:
|
|
case SCR_STINKING_CLOUD:
|
|
case SCR_TAMING:
|
|
case SCR_TELEPORTATION:
|
|
return 20;
|
|
case SCR_GENOCIDE:
|
|
return 30;
|
|
case SCR_BLANK_PAPER:
|
|
default:
|
|
impossible("You can't write such a weird scroll!");
|
|
}
|
|
return 1000;
|
|
}
|
|
|
|
/* getobj callback for object to write on */
|
|
staticfn int
|
|
write_ok(struct obj *obj)
|
|
{
|
|
if (!obj || (obj->oclass != SCROLL_CLASS && obj->oclass != SPBOOK_CLASS))
|
|
return GETOBJ_EXCLUDE;
|
|
|
|
if (obj->otyp == SCR_BLANK_PAPER || obj->otyp == SPE_BLANK_PAPER)
|
|
return GETOBJ_SUGGEST;
|
|
|
|
return GETOBJ_DOWNPLAY;
|
|
}
|
|
|
|
/* write -- applying a magic marker */
|
|
int
|
|
dowrite(struct obj *pen)
|
|
{
|
|
struct obj *paper;
|
|
char namebuf[BUFSZ] = DUMMY, *nm, *bp;
|
|
struct obj *new_obj;
|
|
int basecost, actualcost;
|
|
int curseval;
|
|
char qbuf[QBUFSZ];
|
|
int first, last, i, deferred, deferralchance, real;
|
|
boolean by_descr = FALSE;
|
|
const char *typeword;
|
|
int spell_knowledge;
|
|
|
|
if (nohands(gy.youmonst.data)) {
|
|
You("need hands to be able to write!");
|
|
return ECMD_OK;
|
|
} else if (Glib) {
|
|
pline("%s from your %s.", Tobjnam(pen, "slip"),
|
|
fingers_or_gloves(FALSE));
|
|
dropx(pen);
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* get paper to write on */
|
|
paper = getobj("write on", write_ok, GETOBJ_NOFLAGS);
|
|
if (!paper)
|
|
return ECMD_CANCEL;
|
|
/* can't write on a novel (unless/until it's been converted into a blank
|
|
spellbook), but we want messages saying so to avoid "spellbook" */
|
|
typeword = (paper->otyp == SPE_NOVEL) ? "book"
|
|
: (paper->oclass == SPBOOK_CLASS) ? "spellbook"
|
|
: "scroll";
|
|
if (Blind) {
|
|
if (!paper->dknown) {
|
|
You("don't know whether that %s is blank or not.", typeword);
|
|
return ECMD_OK;
|
|
} else if (paper->oclass == SPBOOK_CLASS) {
|
|
/* can't write a magic book while blind */
|
|
pline("%s can't create braille text.",
|
|
upstart(ysimple_name(pen)));
|
|
return ECMD_OK;
|
|
}
|
|
}
|
|
observe_object(paper);
|
|
if (paper->otyp != SCR_BLANK_PAPER && paper->otyp != SPE_BLANK_PAPER) {
|
|
pline("That %s is not blank!", typeword);
|
|
exercise(A_WIS, FALSE);
|
|
return ECMD_TIME;
|
|
}
|
|
makeknown(SCR_BLANK_PAPER);
|
|
|
|
/* what to write */
|
|
Sprintf(qbuf, "What type of %s do you want to write?", typeword);
|
|
getlin(qbuf, namebuf);
|
|
(void) mungspaces(namebuf); /* remove any excess whitespace */
|
|
if (namebuf[0] == '\033' || !namebuf[0])
|
|
return ECMD_TIME;
|
|
nm = namebuf;
|
|
if (!strncmpi(nm, "scroll ", 7))
|
|
nm += 7;
|
|
else if (!strncmpi(nm, "spellbook ", 10))
|
|
nm += 10;
|
|
if (!strncmpi(nm, "of ", 3))
|
|
nm += 3;
|
|
|
|
if ((bp = strstri(nm, " armour")) != 0) {
|
|
memcpy(bp, " armor ", 7);
|
|
(void) mungspaces(bp + 1); /* remove the extra space */
|
|
}
|
|
|
|
deferred = real = 0; /* not any scroll or book */
|
|
deferralchance = 0; /* incremented for each oc_uname match */
|
|
first = svb.bases[(int) paper->oclass];
|
|
last = svb.bases[(int) paper->oclass + 1] - 1;
|
|
/* first loop: look for match with name/description */
|
|
for (i = first; i <= last; i++) {
|
|
/* extra shufflable descr not representing a real object */
|
|
if (!OBJ_NAME(objects[i]))
|
|
continue;
|
|
|
|
if (!strcmpi(OBJ_NAME(objects[i]), nm)) {
|
|
if (objects[i].oc_name_known
|
|
/* spellbooks can only be written by_name, so no need to
|
|
hold out for a 'better' by_descr match */
|
|
|| paper->oclass == SPBOOK_CLASS) {
|
|
goto found;
|
|
} else {
|
|
/* save item in case there are no better by_descr matches */
|
|
real = deferred = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!strcmpi(OBJ_DESCR(objects[i]), nm)) {
|
|
by_descr = TRUE;
|
|
goto found;
|
|
}
|
|
}
|
|
/* second loop: look for match with user-assigned name */
|
|
/* we will get here if 'nm' isn't a real scroll name/descr, or is the name
|
|
* of a real scroll that hasn't been formally IDed. */
|
|
for (i = first; i <= last; i++) {
|
|
/* player might assign same name multiple times and if so,
|
|
we choose one of those matches randomly */
|
|
if (objects[i].oc_uname && !strcmpi(objects[i].oc_uname, nm)
|
|
/* prefer attempting to write the real scroll type if
|
|
the typename clobbers a real scroll and is known to
|
|
be incorrect */
|
|
&& !(real && objects[i].oc_name_known)
|
|
/*
|
|
* First match: chance incremented to 1,
|
|
* !rn2(1) is 1, we remember i;
|
|
* second match: chance incremented to 2,
|
|
* !rn2(2) has 1/2 chance to replace i;
|
|
* third match: chance incremented to 3,
|
|
* !rn2(3) has 1/3 chance to replace i
|
|
* and 2/3 chance to keep previous 50:50
|
|
* choice; so on for higher match counts.
|
|
*/
|
|
&& !rn2(++deferralchance)) {
|
|
deferred = i;
|
|
/* writing by user-assigned name is same as by description:
|
|
fails for books, works for scrolls (having an assigned
|
|
type name guarantees presence on discoveries list) */
|
|
by_descr = TRUE;
|
|
}
|
|
}
|
|
|
|
if (deferred) {
|
|
i = deferred;
|
|
goto found;
|
|
}
|
|
|
|
There("is no such %s!", typeword);
|
|
return ECMD_TIME;
|
|
found:
|
|
|
|
if (i == SCR_BLANK_PAPER || i == SPE_BLANK_PAPER) {
|
|
You_cant("write that!");
|
|
pline("It's obscene!");
|
|
return ECMD_TIME;
|
|
} else if (i == SPE_NOVEL) {
|
|
boolean fanfic = !rn2(3), tearup = !rn2(3);
|
|
|
|
if (!fanfic) {
|
|
You("%s to write the Great Yendorian Novel, but %s inspiration.",
|
|
!tearup ? "prepare" : "try",
|
|
!Hallucination ? "lack" : "have too much");
|
|
} else {
|
|
You("%sproduce really %s fan-fiction.",
|
|
!tearup ? "start to " : "",
|
|
!Hallucination ? "lame" : "awesome");
|
|
}
|
|
if (!tearup) {
|
|
You("give up on the idea.");
|
|
} else {
|
|
You("tear it up.");
|
|
useup(paper);
|
|
}
|
|
return ECMD_TIME;
|
|
} else if (i == SPE_BOOK_OF_THE_DEAD) {
|
|
pline("No mere dungeon adventurer could write that.");
|
|
return ECMD_TIME;
|
|
} else if (by_descr && paper->oclass == SPBOOK_CLASS
|
|
&& !objects[i].oc_name_known) {
|
|
/* can't write unknown spellbooks by description */
|
|
pline("Unfortunately you don't have enough information to go on.");
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* KMH, conduct */
|
|
if (!u.uconduct.literate++)
|
|
livelog_printf(LL_CONDUCT,
|
|
"became literate by writing %s", an(typeword));
|
|
|
|
new_obj = mksobj(i, FALSE, FALSE);
|
|
new_obj->bknown = (paper->bknown && pen->bknown);
|
|
|
|
/* shk imposes a flat rate per use, not based on actual charges used */
|
|
check_unpaid(pen);
|
|
|
|
/* see if there's enough ink */
|
|
basecost = cost(new_obj);
|
|
if (pen->spe < basecost / 2) {
|
|
Your("marker is too dry to write that!");
|
|
obfree(new_obj, (struct obj *) 0);
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* we're really going to write now, so calculate cost
|
|
*/
|
|
actualcost = rn1(basecost / 2, basecost / 2);
|
|
curseval = bcsign(pen) + bcsign(paper);
|
|
exercise(A_WIS, TRUE);
|
|
/* dry out marker */
|
|
if (pen->spe < actualcost) {
|
|
pen->spe = 0;
|
|
Your("marker dries out!");
|
|
/* scrolls disappear, spellbooks don't */
|
|
if (paper->oclass == SPBOOK_CLASS) {
|
|
pline_The("spellbook is left unfinished and your writing fades.");
|
|
update_inventory(); /* pen charges */
|
|
} else {
|
|
pline_The("scroll is now useless and disappears!");
|
|
useup(paper);
|
|
}
|
|
obfree(new_obj, (struct obj *) 0);
|
|
return ECMD_TIME;
|
|
}
|
|
pen->spe -= actualcost;
|
|
|
|
/*
|
|
* Writing by name requires that the hero knows the scroll or
|
|
* book type. One has previously been read (and its effect
|
|
* was evident) or been ID'd via scroll/spell/throne (or skill
|
|
* for Wizards) and it will be on the discoveries list.
|
|
* Unknown spellbooks can also be written by name if the hero
|
|
* has fresh knowledge of the spell, or if the spell is almost
|
|
* forgotten and the hero is Lucky (with a greater chance than
|
|
* if the spell is unknown or forgotten).
|
|
* (Previous versions allowed scrolls and books to be written
|
|
* by type name if they were on the discoveries list via being
|
|
* given a user-assigned name, even though doing the latter
|
|
* doesn't--and shouldn't--make the actual type become known.)
|
|
*
|
|
* Writing by description requires that the hero knows the
|
|
* description (a scroll's label, that is, since books by_descr
|
|
* are rejected above). This is done by checking to see if a
|
|
* scroll with the same description has been encountered.
|
|
*
|
|
* Normal requirements can be overridden if hero is Lucky.
|
|
*/
|
|
|
|
if (paper->oclass == SPBOOK_CLASS) {
|
|
spell_knowledge = known_spell(new_obj->otyp);
|
|
} else {
|
|
spell_knowledge = spe_Unknown;
|
|
}
|
|
/* if known, then either by-name or by-descr works */
|
|
if (!objects[new_obj->otyp].oc_name_known
|
|
/* else if named, then only by-descr works */
|
|
&& !(by_descr && objects[new_obj->otyp].oc_encountered)
|
|
/* else fresh knowledge of the spell works */
|
|
&& spell_knowledge != spe_Fresh
|
|
/* and Luck might override after previous checks have failed */
|
|
&& rnl(((Role_if(PM_WIZARD) && paper->oclass != SPBOOK_CLASS)
|
|
|| spell_knowledge == spe_GoingStale)
|
|
? 5 : 15)) {
|
|
You("%s to write that.", by_descr ? "fail" : "don't know how");
|
|
/* scrolls disappear, spellbooks don't */
|
|
if (paper->oclass == SPBOOK_CLASS) {
|
|
You(
|
|
"write in your best handwriting: \"My Diary\", but it quickly fades.");
|
|
update_inventory(); /* pen charges */
|
|
} else {
|
|
if (by_descr) {
|
|
Strcpy(namebuf, OBJ_DESCR(objects[new_obj->otyp]));
|
|
wipeout_text(namebuf, (6 + MAXULEV - u.ulevel) / 6, 0);
|
|
} else
|
|
Sprintf(namebuf, "%s was here!", svp.plname);
|
|
You("write \"%s\" and the scroll disappears.", namebuf);
|
|
useup(paper);
|
|
}
|
|
obfree(new_obj, (struct obj *) 0);
|
|
return ECMD_TIME;
|
|
}
|
|
/* can write scrolls when blind, but requires luck too;
|
|
attempts to write books when blind are caught above */
|
|
if (Blind && rnl(3)) {
|
|
/* writing while blind usually fails regardless of
|
|
whether the target scroll is known; even if we
|
|
have passed the write-an-unknown scroll test
|
|
above we can still fail this one, so it's doubly
|
|
hard to write an unknown scroll while blind */
|
|
You("fail to write the scroll correctly and it disappears.");
|
|
useup(paper);
|
|
obfree(new_obj, (struct obj *) 0);
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* useup old scroll / spellbook */
|
|
useup(paper);
|
|
|
|
/* success */
|
|
if (new_obj->oclass == SPBOOK_CLASS) {
|
|
/* acknowledge the change in the object's description... */
|
|
pline_The("spellbook warps strangely, then turns %s.",
|
|
new_book_description(new_obj->otyp, namebuf));
|
|
}
|
|
new_obj->blessed = (curseval > 0);
|
|
new_obj->cursed = (curseval < 0);
|
|
#ifdef MAIL_STRUCTURES
|
|
if (new_obj->otyp == SCR_MAIL)
|
|
/* 0: delivered in-game via external event (or randomly for fake mail);
|
|
1: from bones or wishing; 2: written with marker */
|
|
new_obj->spe = 2;
|
|
#endif
|
|
/* unlike alchemy, for example, a successful result yields the
|
|
specifically chosen item so hero recognizes it even if blind;
|
|
the exception is for being lucky writing an undiscovered scroll,
|
|
where the label associated with the type-name isn't known yet;
|
|
but if writing by description, the description is always known */
|
|
new_obj->dknown = FALSE;
|
|
if (objects[new_obj->otyp].oc_name_known || by_descr)
|
|
observe_object(new_obj);
|
|
|
|
new_obj = hold_another_object(new_obj, "Oops! %s out of your grasp!",
|
|
The(aobjnam(new_obj, "slip")),
|
|
(const char *) 0);
|
|
nhUse(new_obj); /* try to avoid complaint about dead assignment */
|
|
return ECMD_TIME;
|
|
}
|
|
|
|
/* most book descriptions refer to cover appearance, so we can issue a
|
|
message for converting a plain book into one of those with something
|
|
like "the spellbook turns red" or "the spellbook turns ragged";
|
|
but some descriptions refer to composition and "the book turns vellum"
|
|
looks funny, so we want to insert "into " prior to such descriptions;
|
|
even that's rather iffy, indicating that such descriptions probably
|
|
ought to be eliminated (especially "cloth"!) */
|
|
staticfn char *
|
|
new_book_description(int booktype, char *outbuf)
|
|
{
|
|
/* subset of description strings from objects.c; if it grows
|
|
much, we may need to add a new flag field to objects[] instead */
|
|
static const char *const compositions[] = {
|
|
"parchment",
|
|
"vellum",
|
|
"cloth",
|
|
#if 0
|
|
"canvas", "hardcover", /* not used */
|
|
"papyrus", /* not applicable--can't be produced via writing */
|
|
#endif /*0*/
|
|
0
|
|
};
|
|
const char *descr, *const *comp_p;
|
|
|
|
descr = OBJ_DESCR(objects[booktype]);
|
|
for (comp_p = compositions; *comp_p; ++comp_p)
|
|
if (!strcmpi(descr, *comp_p))
|
|
break;
|
|
|
|
Sprintf(outbuf, "%s%s", *comp_p ? "into " : "", descr);
|
|
return outbuf;
|
|
}
|
|
|
|
/*write.c*/
|