Overhaul of priest donations

The old code had two main problems: a) it was very difficult for
unspoiled players to figure out how it worked (because donating too
much got you a bad result, and the exact amount you needed depended
on magic numbers that weren't stated in game, and because you had to
hide your visible gold to get a good result); b) for players who
knew the mechanics, it was somewhat exploitable and also somewhat
tedious to make use of (due to needing to hide visible gold before
donating).

This change preserves the spirit of the previous code whilst making
things more transparent for new players and less tedious for existing
players: the donation amounts for the various effects are still
roughly the same (but randomized), but the amounts you need to donate
for clairvoyance and for protection are explicitly stated (and as
before, the alignment reset is done by donating an unnecessarily
large amount and isn't explicitly stated as an option).  If you have
a lot of visible gold, you still need to donate a sizeable proportion
of it to get a useful effect, but now you get a larger reward to
compensate for the larger donation (to the extent that doing this
gives comparable results to doing it as a series of small donations,
removing the incentive to hide your gold before donating).

There's also something here for those players who like to squeeze
every last point of optimality out of a game: the "obvious" donation
strategy gives decent results, but players who are really willing to
dig into the mechanics may be able to find a way to get slightly
better results on average (which if I've balanced this correctly,
will lead to a very long and complicated spoiler).

One other change is that this is now based on your peak rather than
current level, to fix an exploit in which the character was drained
down to level 1 to donate a very large amount of gold (improving by
20 AC points) and then immediately restored back to the previous
experience level using a blessed potion of restore ablity.

This breaks save compatibility, but is being pushed together with
other save-breaking changes to avoid the need for multiple bumps to
EDITLEVEL.
This commit is contained in:
Alex Smith
2026-03-19 05:10:09 +00:00
parent 4d043a6f9c
commit e6d44c68e8
5 changed files with 64 additions and 23 deletions

View File

@@ -1582,6 +1582,9 @@ healers may get tiny damage increase when attacking with knives
allow rogues to also backstab sleeping or paralyzed monsters allow rogues to also backstab sleeping or paralyzed monsters
rogues cannot backstab monsters that have no backside rogues cannot backstab monsters that have no backside
give experience if opening Schroedinger's Box causes death of the cat inside give experience if opening Schroedinger's Box causes death of the cat inside
priest donation amounts are explicitly stated, randomized slightly, based on
peak rather than current level, and allow for bulk donations (buying
larger amounts of clairvoyance/protection) if you have a lot of gold
Fixes to 3.7.0-x General Problems Exposed Via git Repository Fixes to 3.7.0-x General Problems Exposed Via git Repository

View File

@@ -1545,7 +1545,7 @@ extern int monster_census(boolean);
extern int msummon(struct monst *); extern int msummon(struct monst *);
extern void summon_minion(aligntyp, boolean); extern void summon_minion(aligntyp, boolean);
extern int demon_talk(struct monst *) NONNULLARG1; extern int demon_talk(struct monst *) NONNULLARG1;
extern long bribe(struct monst *) NONNULLARG1; extern long bribe(struct monst *, const char *) NONNULLARG12;
extern int dprince(aligntyp); extern int dprince(aligntyp);
extern int dlord(aligntyp); extern int dlord(aligntyp);
extern int llord(void); extern int llord(void);

View File

@@ -98,6 +98,7 @@ struct epri {
schar shroom; /* index in rooms */ schar shroom; /* index in rooms */
coord shrpos; /* position of shrine */ coord shrpos; /* position of shrine */
d_level shrlevel; /* level (& dungeon) of shrine */ d_level shrlevel; /* level (& dungeon) of shrine */
unsigned cheapskate_count; /* number of cheapskate donations */
long intone_time, /* used to limit verbosity +*/ long intone_time, /* used to limit verbosity +*/
enter_time, /*+ of temple entry messages */ enter_time, /*+ of temple entry messages */
hostile_time, /* forbidding feeling */ hostile_time, /* forbidding feeling */

View File

@@ -332,7 +332,8 @@ demon_talk(struct monst *mtmp)
else if (canseemon(mtmp)) else if (canseemon(mtmp))
pline("%s seems to be demanding something.", Amonnam(mtmp)); pline("%s seems to be demanding something.", Amonnam(mtmp));
offer = 0L; offer = 0L;
if (!Deaf && ((offer = bribe(mtmp)) >= demand)) { if (!Deaf &&
((offer = bribe(mtmp, "How much will you offer?")) >= demand)) {
pline("%s vanishes, laughing about cowardly mortals.", pline("%s vanishes, laughing about cowardly mortals.",
Amonnam(mtmp)); Amonnam(mtmp));
} else if (offer > 0L } else if (offer > 0L
@@ -357,13 +358,13 @@ demon_talk(struct monst *mtmp)
} }
long long
bribe(struct monst *mtmp) bribe(struct monst *mtmp, const char *prompt)
{ {
char buf[BUFSZ] = DUMMY; char buf[BUFSZ] = DUMMY;
long offer; long offer;
long umoney = money_cnt(gi.invent); long umoney = money_cnt(gi.invent);
getlin("How much will you offer?", buf); getlin(prompt, buf);
if (sscanf(buf, "%ld", &offer) != 1) if (sscanf(buf, "%ld", &offer) != 1)
offer = 0L; offer = 0L;

View File

@@ -559,6 +559,8 @@ priest_talk(struct monst *priest)
{ {
boolean coaligned = p_coaligned(priest); boolean coaligned = p_coaligned(priest);
boolean strayed = (u.ualign.record < 0); boolean strayed = (u.ualign.record < 0);
unsigned *cheapskate = NULL;
if (EPRI(priest)) cheapskate = &EPRI(priest)->cheapskate_count;
/* /*
* Note: we won't be called if hero is Deaf [since dochat() will * Note: we won't be called if hero is Deaf [since dochat() will
@@ -625,53 +627,87 @@ priest_talk(struct monst *priest)
pline("%s is not interested.", Monnam(priest)); pline("%s is not interested.", Monnam(priest));
return; return;
} else { } else {
/* there's now some randomization in how much you need to donate, but
you are given suggested donation values that will guarantee
clairvoyance and protection respectively; with more gold visible
you need to donate more but get a greater effect; and if you
cheapskate out to rerandomize the donation amounts they will be
higher next time */
long offer; long offer;
long suggested = (u.ulevelpeak ? u.ulevelpeak : 1 ) *
rn1(101, 150 + (cheapskate ? *cheapskate : 0) * 40);
long quan = money_cnt(gi.invent) / (suggested * 3);
char buf[BUFSZ];
pline("%s asks you for a contribution for the temple.", if (quan < 1)
Monnam(priest)); quan = 1;
if ((offer = bribe(priest)) == 0) {
Sprintf(buf, "How much will you offer (suggested: %ld or %ld)?",
suggested * quan, suggested * quan * 2);
if (flags.debug)
pline("%s asks you for a contribution for the temple (base %ld).",
Monnam(priest), suggested);
else
pline("%s asks you for a contribution for the temple.",
Monnam(priest));
if ((offer = bribe(priest, buf)) == 0) {
SetVoice(priest, 0, 80, 0); SetVoice(priest, 0, 80, 0);
verbalize("Thou shalt regret thine action!"); verbalize("Thou shalt regret thine action!");
if (coaligned) if (coaligned)
adjalign(-1); adjalign(-1);
} else if (offer < (u.ulevel * 200)) { if (cheapskate) ++*cheapskate;
} else if (offer < suggested * quan) {
if (money_cnt(gi.invent) > (offer * 2L)) { if (money_cnt(gi.invent) > (offer * 2L)) {
SetVoice(priest, 0, 80, 0); SetVoice(priest, 0, 80, 0);
verbalize("Cheapskate."); verbalize("Cheapskate.");
if (cheapskate) ++*cheapskate;
} else { } else {
SetVoice(priest, 0, 80, 0); SetVoice(priest, 0, 80, 0);
verbalize("I thank thee for thy contribution."); verbalize("I thank thee for thy contribution.");
/* give player some token */ /* give player some token */
exercise(A_WIS, TRUE); exercise(A_WIS, TRUE);
} }
} else if (offer < (u.ulevel * 400)) { } else if (offer < suggested * quan * 2) {
SetVoice(priest, 0, 80, 0); SetVoice(priest, 0, 80, 0);
verbalize("Thou art indeed a pious individual."); verbalize("Thou art indeed a pious individual.");
if (money_cnt(gi.invent) < (offer * 2L)) { if (money_cnt(gi.invent) < (offer * 2L)) {
if (coaligned && u.ualign.record <= ALGN_SINNED) if (coaligned && u.ualign.record <= ALGN_SINNED)
adjalign(1); adjalign(1);
verbalize("I bestow upon thee a blessing.");
incr_itimeout(&HClairvoyant, rn1(500, 500));
} }
} else if (offer < (u.ulevel * 600) verbalize("I bestow upon thee a blessing.");
/* u.ublessed is only active when Protection is incr_itimeout(&HClairvoyant, rn1(500 * offer / suggested,
enabled via something other than worn gear 500 * offer / suggested));
(theft by gremlin clears the intrinsic but not } else if (offer < suggested * quan * 3) {
its former magnitude, making it recoverable) */ int orig_ublessed = u.ublessed;
&& (!(HProtection & INTRINSIC)
|| (u.ublessed < 20 /* u.ublessed is only active when Protection is enabled via
&& (u.ublessed < 9 || !rn2(u.ublessed))))) { something other than worn gear (theft by gremlin clears the
SetVoice(priest, 0, 80, 0); intrinsic but not its former magnitude, making it
verbalize("Thou hast been rewarded for thy devotion."); recoverable) */
if (!(HProtection & INTRINSIC)) { if (!(HProtection & INTRINSIC)) {
HProtection |= FROMOUTSIDE; HProtection |= FROMOUTSIDE;
orig_ublessed = -1; /* force "rewarded" message */
}
for (; offer >= (2 * suggested); offer -= (2 * suggested)) {
if (!u.ublessed) if (!u.ublessed)
u.ublessed = rn1(3, 2); u.ublessed = rn1(3, 2);
} else else if (u.ublessed < 20 &&
u.ublessed++; (u.ublessed < 9 || !rn2(u.ublessed)))
u.ublessed++;
}
SetVoice(priest, 0, 80, 0);
if (u.ublessed > orig_ublessed) {
verbalize("Thou hast been rewarded for thy devotion.");
} else {
verbalize("Thy selfless generosity is deeply appreciated.");
}
} else { } else {
SetVoice(priest, 0, 80, 0); SetVoice(priest, 0, 80, 0);
verbalize("Thy selfless generosity is deeply appreciated."); verbalize("Thy selfless generosity is deeply appreciated.");
/* money_cnt check is preserved for futureproofing but probably
can't fail in the current code */
if (money_cnt(gi.invent) < (offer * 2L) && coaligned) { if (money_cnt(gi.invent) < (offer * 2L) && coaligned) {
if (strayed && (svm.moves - u.ucleansed) > 5000L) { if (strayed && (svm.moves - u.ucleansed) > 5000L) {
u.ualign.record = 0; /* cleanse thee */ u.ualign.record = 0; /* cleanse thee */