From 420f412e1c69901d0a892792a4ee2e71a87310c2 Mon Sep 17 00:00:00 2001 From: copperwater Date: Sat, 21 Apr 2018 15:30:17 -0400 Subject: [PATCH 01/36] Port NetHack4 colored flashes when dropping a container on an altar This is a very popular quality-of-life feature, providing a quick way to identify the beatitude of everything in the container without needing to wade through pages of menus extracting and then re-stashing items. If hallucinating, you will only see "pretty multichromatic flashes" and items will not have their beatitude identified. If you see *no* flashes (meaning everything was uncursed), the items will all be identified as uncursed. Either way, atheist conduct is still broken in every case (unless blind, which is already caught by doaltarobj). Locked containers produce no flashes and give no insight on the beatitude of items inside them. --- src/do.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/do.c b/src/do.c index 580c701c09..2e5301d1b3 100644 --- a/src/do.c +++ b/src/do.c @@ -294,6 +294,50 @@ register struct obj *obj; if (obj->oclass != COIN_CLASS) obj->bknown = 1; /* ok to bypass set_bknown() */ } + + /* From NetHack4: colored flashes one level deep inside containers. */ + if (Has_contents(obj) && !obj->olocked && obj->cknown) { + int blessed = 0; + int cursed = 0; + struct obj * otmp; + for (otmp = obj->cobj; otmp; otmp = otmp->nobj) { + if (otmp->blessed) + blessed++; + if (otmp->cursed) + cursed++; + if (!Hallucination) { + otmp->bknown = 1; + } + } + /* even when hallucinating, if you get no flashes at all, you know + * everything's uncursed, so save the player the trouble of manually + * naming them all */ + if (Hallucination && blessed + cursed == 0) { + for (otmp = obj->cobj; otmp; otmp = otmp->nobj) { + otmp->bknown = 1; + } + } + if (blessed + cursed > 0) { + const char* color; + if (Hallucination) { + color = "pretty multichromatic"; + } + else if (blessed == 0) { + color = hcolor(NH_BLACK); + } + else if (cursed == 0) { + color = hcolor(NH_AMBER); + } + else { + color = "colored"; + } + + pline("From inside %s, you see %s flash%s.", + the(xname(obj)), + (blessed + cursed == 1 ? an(color) : color), + (blessed + cursed == 1 ? "" : "es")); + } + } } static void From a78d58b78a96d62558b8ce6d37866b41f026b0e1 Mon Sep 17 00:00:00 2001 From: copperwater Date: Tue, 5 Feb 2019 15:42:34 -0500 Subject: [PATCH 02/36] Make all orcish creatures resist poison This is a really weird inconsistency. Player orcs are poison resistant, and certain orcs use poisoned ammunition with abandon, yet none of them actually resist poison, as can easily be seen by flinging poison darts or arrows at a horde of hill orcs until one of them is instakilled. Fix this by making all types of orcish monsters poison resistant. Bugbears in xNetHack, being a sort of orc, are moved into the o class, and thus also became poison resistant. I considered them sufficiently orcish to also make them poison resistant in this commit, though they remain in the h class for now. --- src/monst.c | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/monst.c b/src/monst.c index 9bbce0a277..566f2fccf3 100644 --- a/src/monst.c +++ b/src/monst.c @@ -434,8 +434,9 @@ NEARDATA struct permonst mons_init[] = { MON("bugbear", S_HUMANOID, LVL(3, 9, 5, 0, -6), (G_GENO | 1), A(ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1250, 250, MS_GROWL, MZ_LARGE), 0, 0, M1_HUMANOID | M1_OMNIVORE, - M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, 5, CLR_BROWN), + SIZ(1250, 250, MS_GROWL, MZ_LARGE), MR_POISON, 0, + M1_HUMANOID | M1_OMNIVORE, M2_STRONG | M2_COLLECT, + M3_INFRAVISIBLE | M3_INFRAVISION, 5, CLR_BROWN), MON("dwarf lord", S_HUMANOID, LVL(4, 6, 10, 10, 5), (G_GENO | 2), A(ATTK(AT_WEAP, AD_PHYS, 2, 4), ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), @@ -619,50 +620,57 @@ NEARDATA struct permonst mons_init[] = { MON("goblin", S_ORC, LVL(0, 6, 10, 0, -3), (G_GENO | 2), A(ATTK(AT_WEAP, AD_PHYS, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(400, 100, MS_ORC, MZ_SMALL), 0, 0, M1_HUMANOID | M1_OMNIVORE, - M2_ORC | M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, 1, CLR_GRAY), + SIZ(400, 100, MS_ORC, MZ_SMALL), MR_POISON, 0, + M1_HUMANOID | M1_OMNIVORE, M2_ORC | M2_COLLECT, + M3_INFRAVISIBLE | M3_INFRAVISION, 1, CLR_GRAY), MON("hobgoblin", S_ORC, LVL(1, 9, 10, 0, -4), (G_GENO | 2), A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1000, 200, MS_ORC, MZ_HUMAN), 0, 0, M1_HUMANOID | M1_OMNIVORE, - M2_ORC | M2_STRONG | M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, - 3, CLR_BROWN), + SIZ(1000, 200, MS_ORC, MZ_HUMAN), MR_POISON, 0, + M1_HUMANOID | M1_OMNIVORE, M2_ORC | M2_STRONG | M2_COLLECT, + M3_INFRAVISIBLE | M3_INFRAVISION, 3, CLR_BROWN), /* plain "orc" for zombie corpses only; not created at random */ MON("orc", S_ORC, LVL(1, 9, 10, 0, -3), (G_GENO | G_NOGEN | G_LGROUP), A(ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(850, 150, MS_ORC, MZ_HUMAN), 0, 0, M1_HUMANOID | M1_OMNIVORE, + SIZ(850, 150, MS_ORC, MZ_HUMAN), MR_POISON, 0, + M1_HUMANOID | M1_OMNIVORE, M2_NOPOLY | M2_ORC | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, 3, CLR_RED), MON("hill orc", S_ORC, LVL(2, 9, 10, 0, -4), (G_GENO | G_LGROUP | 2), A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1000, 200, MS_ORC, MZ_HUMAN), 0, 0, M1_HUMANOID | M1_OMNIVORE, + SIZ(1000, 200, MS_ORC, MZ_HUMAN), MR_POISON, 0, + M1_HUMANOID | M1_OMNIVORE, M2_ORC | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, 4, CLR_YELLOW), MON("Mordor orc", S_ORC, LVL(3, 5, 10, 0, -5), (G_GENO | G_LGROUP | 1), A(ATTK(AT_WEAP, AD_PHYS, 1, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1200, 200, MS_ORC, MZ_HUMAN), 0, 0, M1_HUMANOID | M1_OMNIVORE, + SIZ(1200, 200, MS_ORC, MZ_HUMAN), MR_POISON, 0, + M1_HUMANOID | M1_OMNIVORE, M2_ORC | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, 5, CLR_BLUE), MON("Uruk-hai", S_ORC, LVL(3, 7, 10, 0, -4), (G_GENO | G_LGROUP | 1), A(ATTK(AT_WEAP, AD_PHYS, 1, 8), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1300, 300, MS_ORC, MZ_HUMAN), 0, 0, M1_HUMANOID | M1_OMNIVORE, + SIZ(1300, 300, MS_ORC, MZ_HUMAN), MR_POISON, 0, + M1_HUMANOID | M1_OMNIVORE, M2_ORC | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, 5, CLR_BLACK), MON("orc shaman", S_ORC, LVL(3, 9, 5, 10, -5), (G_GENO | 1), A(ATTK(AT_MAGC, AD_SPEL, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1000, 300, MS_ORC, MZ_HUMAN), 0, 0, M1_HUMANOID | M1_OMNIVORE, + SIZ(1000, 300, MS_ORC, MZ_HUMAN), MR_POISON, 0, + M1_HUMANOID | M1_OMNIVORE, M2_ORC | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_MAGIC, M3_INFRAVISIBLE | M3_INFRAVISION, 5, HI_ZAP), MON("orc-captain", S_ORC, LVL(5, 5, 10, 0, -5), (G_GENO | 1), A(ATTK(AT_WEAP, AD_PHYS, 2, 4), ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), - SIZ(1350, 350, MS_ORC, MZ_HUMAN), 0, 0, M1_HUMANOID | M1_OMNIVORE, + SIZ(1350, 350, MS_ORC, MZ_HUMAN), MR_POISON, 0, + M1_HUMANOID | M1_OMNIVORE, M2_ORC | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, 7, HI_LORD), /* From 22484e7e0820174731353ca57264d5df8bd0df20 Mon Sep 17 00:00:00 2001 From: copperwater Date: Sun, 12 Jan 2020 17:32:40 -0500 Subject: [PATCH 03/36] Consider the dwarvish roundshield and Uruk-hai shield to be "bulky" These seem like they would be bulky sorts of shields, so set the bit to true on them. This doesn't affect any game mechanics. --- src/objects.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/objects.c b/src/objects.c index 782e8019f3..59269cd139 100644 --- a/src/objects.c +++ b/src/objects.c @@ -480,13 +480,13 @@ SHIELD("small shield", None, SHIELD("elven shield", "blue and green shield", 0, 0, 0, 0, 2, 0, 40, 7, 8, 0, WOOD, CLR_GREEN), SHIELD("Uruk-hai shield", "white-handed shield", - 0, 0, 0, 0, 2, 0, 50, 7, 9, 0, IRON, HI_METAL), + 0, 0, 1, 0, 2, 0, 50, 7, 9, 0, IRON, HI_METAL), SHIELD("orcish shield", "red-eyed shield", 0, 0, 0, 0, 2, 0, 50, 7, 9, 0, IRON, CLR_RED), SHIELD("large shield", None, 1, 0, 1, 0, 7, 0, 100, 10, 8, 0, IRON, HI_METAL), SHIELD("dwarvish roundshield", "large round shield", - 0, 0, 0, 0, 4, 0, 100, 10, 8, 0, IRON, HI_METAL), + 0, 0, 1, 0, 4, 0, 100, 10, 8, 0, IRON, HI_METAL), SHIELD("shield of reflection", "polished silver shield", 0, 1, 0, REFLECTING, 3, 0, 50, 50, 8, 0, SILVER, HI_SILVER), From da766901f93b5b03da58e73b17dfa60228e22fb1 Mon Sep 17 00:00:00 2001 From: copperwater Date: Thu, 13 Sep 2018 23:44:25 -0400 Subject: [PATCH 04/36] Force bolts destroy iron bars Feature ported from dNetHack, with slight changes (it wakes monsters, produces different messages, and doesn't let this particular ray travel any farther). The main gameplay implications are probably that it's easier to get into Orcish Town now (an orc from inside might even blast them out for you) and that iron-barred closets with prizes inside will be a little more accessible. Currently iron bars don't play a critical player-containment role on any other special levels. If the iron bars are marked nondiggable, they won't be blown up, just like they can't be dissolved with acid. For some reason, I thought I remembered implementing a message like "The iron bars vibrate violently but remain intact" if hit by a force bolt and the bars are nondiggable, but apparently not, as the code isn't in xNetHack. --- src/zap.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/zap.c b/src/zap.c index 3910b909a9..2fd4632a32 100644 --- a/src/zap.c +++ b/src/zap.c @@ -3353,6 +3353,21 @@ struct obj **pobj; /* object tossed/used, set to NULL typ = levl[g.bhitpos.x][g.bhitpos.y].typ; + if (typ == IRONBARS && weapon == ZAPPED_WAND + && ((levl[g.bhitpos.x][g.bhitpos.y].wall_info & W_NONDIGGABLE) == 0) + && (obj->otyp == SPE_FORCE_BOLT || obj->otyp == WAN_STRIKING)) { + levl[g.bhitpos.x][g.bhitpos.y].typ = ROOM; + if (cansee(g.bhitpos.x, g.bhitpos.y)) + pline_The("iron bars are blown apart!"); + else + You_hear("a lot of loud clanging sounds!"); + wake_nearto(g.bhitpos.x, g.bhitpos.y, 20 * 20); + newsym(g.bhitpos.x, g.bhitpos.y); + /* stop the bolt here; it takes a lot of energy to destroy bars */ + range = 0; + break; + } + /* iron bars will block anything big enough and break some things */ if (weapon == THROWN_WEAPON || weapon == KICKED_WEAPON) { if (obj->lamplit && !Blind) From 387df88a4771d616559e434781d0a134ff36455d Mon Sep 17 00:00:00 2001 From: copperwater Date: Thu, 13 Dec 2018 11:04:41 -0500 Subject: [PATCH 05/36] Blessed and uncursed wands wrest much more often Like in UnNetHack, blessed wands wrest 1/7 of the time, and uncursed wrest 1/23 of the time. Cursed wands still wrest 1/121 of the time. This is intended as a minor buff to having a blessed wand, and making it feasible to wrest uncursed wands without spending fifty or more turns zapping it to no effect. Ideally, I would have blessed wands identify that they have 0 charges when you zap them and nothing happens (and never wrest if you didn't know it had 0 charges), to prevent an accidental 1/7-probability wrest. However, there's no way to have the charges be known without also having the recharge count be known, which doesn't make any sense for the hero to know. And it's certainly not worth putting in another known flag just for this. The only place where I can see this biting someone is that if you zap a wand of wishing until you get "Nothing happens" without formally identifying it, there's now a 1/23 chance that you wrest and lose it instead of a 1/121 chance. However, doing that under the old behavior could still randomly wrest it, and most spoiled players already know to formally identify a wand of wishing as soon as they learn what it is. --- src/zap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zap.c b/src/zap.c index 2fd4632a32..927e409c8f 100644 --- a/src/zap.c +++ b/src/zap.c @@ -2210,7 +2210,8 @@ int zappable(wand) register struct obj *wand; { - if (wand->spe < 0 || (wand->spe == 0 && rn2(121))) + int wrestchance = (wand->blessed ? 7 : (wand->cursed ? 121 : 23)); + if (wand->spe < 0 || (wand->spe == 0 && rn2(wrestchance))) return 0; if (wand->spe == 0) You("wrest one last charge from the worn-out wand."); From f191615d347154a210cb5cb12975ead24a45c64d Mon Sep 17 00:00:00 2001 From: copperwater Date: Sat, 24 Feb 2018 13:08:41 -0500 Subject: [PATCH 06/36] Kicking sinks gives boring Klunk! 2/3 of the time instead of 4/5 If the player is trying to kick a sink to get some effect, there's no real advantage in making them try it 20 times before something happens if 10 will do. This makes it much more likely for something to happen when a sink is kicked. --- src/dokick.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dokick.c b/src/dokick.c index 5564c84c5d..5852d43245 100644 --- a/src/dokick.c +++ b/src/dokick.c @@ -1155,7 +1155,7 @@ dokick() if (Levitation) goto dumb; - if (rn2(5)) { + if (rn2(3)) { if (!Deaf) pline("Klunk! The pipes vibrate noisily."); else From 647a7f365637a58637771664eebb6afb72ac84d7 Mon Sep 17 00:00:00 2001 From: copperwater Date: Fri, 1 Feb 2019 13:02:00 -0500 Subject: [PATCH 07/36] Confused scrolls of earth summon earthy monsters This is another SliceHack feature, but again heavily modified. This gets rid of the current pointless confused effect of dropping rocks instead of boulders; earth elementals and dust vortices get created instead. Unlike SliceHack, there is no possibility of causing an earthquake; instead, varying the beatitude varies the number of monsters created instead. Also unlike SliceHack, this doesn't really care about difficulty and won't go out of its way to guarantee certain numbers of vortices or elementals; the magic of the scroll is what it is. Furthermore, the generated monsters aren't forced to be hostile. (Vortices are always hostile anyway; earth elementals might be peaceful for neutrals, and if so, good for them.) --- src/read.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/read.c b/src/read.c index 42b1040a2e..c036147da7 100644 --- a/src/read.c +++ b/src/read.c @@ -1570,7 +1570,16 @@ struct obj *sobj; /* scroll, or fake spellbook object for scroll-like spell */ } case SCR_EARTH: /* TODO: handle steeds */ - if (!Is_rogue_level(&u.uz) && has_ceiling(&u.uz) + if (confused) { + /* create earth elementals and dust vortices */ + int i; + for (i = 0; i < (2 * (2 + sblessed - scursed)) - 1; ++i) { + makemon(&mons[(rn2(3) ? PM_EARTH_ELEMENTAL : PM_DUST_VORTEX)], + u.ux, u.uy, MM_NOWAIT); + } + pline("The earth moves around you!"); + } + else if (!Is_rogue_level(&u.uz) && has_ceiling(&u.uz) && (!In_endgame(&u.uz) || Is_earthlevel(&u.uz))) { register int x, y; int nboulders = 0; @@ -1594,13 +1603,13 @@ struct obj *sobj; /* scroll, or fake spellbook object for scroll-like spell */ && !IS_AIR(levl[x][y].typ) && (x != u.ux || y != u.uy)) { nboulders += - drop_boulder_on_monster(x, y, confused, TRUE); + drop_boulder_on_monster(x, y, FALSE, TRUE); } } } /* Attack the player */ if (!sblessed) { - drop_boulder_on_player(confused, !scursed, TRUE, FALSE); + drop_boulder_on_player(FALSE, !scursed, TRUE, FALSE); } else if (!nboulders) pline("But nothing else happens."); } From 475566ea9a977fc3f73ea5cac6cfa23265782cb3 Mon Sep 17 00:00:00 2001 From: copperwater Date: Sat, 3 Feb 2018 17:35:06 -0500 Subject: [PATCH 08/36] Allow the player to wish for a magic lamp This is a widely popular and freqently requested feature. I consider it to be acceptable since the player is, in the best case, forfeiting a 100% chance of a wish right now for an 80% chance of a wish later. There's no way for it to be turned into multiple wishes. It can theoretically be reused to generate multiple oil lamps, but this will eventually end when a djinni does something other than give a wish, and the light from a magic lamp is infinite anyway. This also allows players to spend a wish from something that isn't a magic lamp on a magic lamp so that they have an infinite light source. --- src/objnam.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/objnam.c b/src/objnam.c index cbc539f20d..32520e3c37 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -4075,9 +4075,6 @@ struct obj *no_wish; case SPE_BOOK_OF_THE_DEAD: typ = SPE_BLANK_PAPER; break; - case MAGIC_LAMP: - typ = OIL_LAMP; - break; default: /* catch any other non-wishable objects (venom) */ if (objects[typ].oc_nowish) From 9d6f72ddb8b42845b0e0f668c50cf95c1524bc91 Mon Sep 17 00:00:00 2001 From: copperwater Date: Tue, 25 Sep 2018 13:47:13 -0400 Subject: [PATCH 09/36] Identify a wand when it prints an unambiguous message while engraving This is a fairly popular suggestion. It does the same thing as other items identifying themselves when they are used and something unambiguous happens, and it saves the player having to manually type-name the wand. The wand does not get named if there is any ambiguity. Some variants have done clever things with things in this vein (e.g. if the bugs on the floor stop moving and you have already identified sleep, they will auto-identify it as a wand of death for you) but that is not the case here. --- src/engrave.c | 69 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/src/engrave.c b/src/engrave.c index 09ddfcc48a..59fde4acfe 100644 --- a/src/engrave.c +++ b/src/engrave.c @@ -6,6 +6,7 @@ #include "hack.h" static const char *NDECL(blengr); +static void FDECL(engraving_learn_wand, (struct obj*)); char * random_engraving(outbuf) @@ -468,20 +469,21 @@ static NEARDATA const char styluses[] = { ALL_CLASSES, ALLOW_NONE, int doengrave() { - 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 */ - xchar type = DUST; /* Type of engraving made */ + boolean dengr = FALSE; /* TRUE if we wipe out the current engraving */ + boolean doblind = FALSE; /* TRUE if engraving blinds the player */ + boolean preknown = FALSE; /* TRUE if we identify the stylus before */ + boolean postknown = FALSE; /* TRUE if we identify the stylus after */ + 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 */ + xchar type = DUST; /* Type of engraving made */ xchar 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 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/...) */ @@ -658,6 +660,8 @@ doengrave() case WAN_WISHING: case WAN_ENLIGHTENMENT: zapnodir(otmp); + /* pre/postknown not needed; these will make it known if + * applicable */ break; /* IMMEDIATE wands */ /* If wand is "IMMEDIATE", remember to affect the @@ -666,17 +670,20 @@ doengrave() case WAN_STRIKING: Strcpy(post_engr_text, "The wand unsuccessfully fights your attempt to write!"); + postknown = TRUE; break; case WAN_SLOW_MONSTER: if (!Blind) { Sprintf(post_engr_text, "The bugs on the %s slow down!", surface(u.ux, u.uy)); + postknown = TRUE; } break; case WAN_SPEED_MONSTER: if (!Blind) { Sprintf(post_engr_text, "The bugs on the %s speed up!", surface(u.ux, u.uy)); + postknown = TRUE; } break; case WAN_POLYMORPH: @@ -684,6 +691,7 @@ doengrave() if (!Blind) { type = (xchar) 0; /* random */ (void) random_engraving(buf); + postknown = TRUE; } else { /* keep the same type so that feels don't change and only the text is altered, @@ -709,6 +717,7 @@ doengrave() Sprintf(post_engr_text, "The %s is riddled by bullet holes!", surface(u.ux, u.uy)); + postknown = TRUE; } break; /* can't tell sleep from death - Eric Backus */ @@ -720,9 +729,11 @@ doengrave() } break; case WAN_COLD: - if (!Blind) + if (!Blind) { Strcpy(post_engr_text, "A few ice cubes drop from the wand."); + postknown = TRUE; + } if (!oep || (oep->engr_type != BURN)) break; /*FALLTHRU*/ @@ -750,7 +761,7 @@ doengrave() if (!objects[otmp->otyp].oc_name_known) { if (flags.verbose) pline("This %s is a wand of digging!", xname(otmp)); - doknown = TRUE; + preknown = TRUE; } Strcpy(post_engr_text, (Blind && !Deaf) @@ -773,7 +784,7 @@ doengrave() if (!objects[otmp->otyp].oc_name_known) { if (flags.verbose) pline("This %s is a wand of fire!", xname(otmp)); - doknown = TRUE; + preknown = TRUE; } Strcpy(post_engr_text, Blind ? "You feel the wand heat up." : "Flames fly from the wand."); @@ -784,7 +795,7 @@ doengrave() if (!objects[otmp->otyp].oc_name_known) { if (flags.verbose) pline("This %s is a wand of lightning!", xname(otmp)); - doknown = TRUE; + preknown = TRUE; } if (!Blind) { Strcpy(post_engr_text, "Lightning arcs from the wand."); @@ -890,10 +901,8 @@ doengrave() */ /* Identify stylus */ - if (doknown) { - learnwand(otmp); - if (objects[otmp->otyp].oc_name_known) - more_experienced(0, 10); + if (preknown) { + engraving_learn_wand(otmp); } if (teleengr) { rloc_engr(oep); @@ -1153,8 +1162,12 @@ doengrave() /* Put the engraving onto the map */ make_engr_at(u.ux, u.uy, buf, g.moves - g.multi, type); - if (post_engr_text[0]) + if (post_engr_text[0]) { pline("%s", post_engr_text); + if (postknown) { + engraving_learn_wand(otmp); + } + } if (doblind && !resists_blnd(&g.youmonst)) { You("are blinded by the flash!"); make_blinded((long) rnd(50), FALSE); @@ -1164,6 +1177,18 @@ doengrave() return 1; } +/* Learn what a wand is by engraving with it. */ +static void +engraving_learn_wand(obj) +struct obj* obj; +{ + learnwand(obj); + /* For some reason, this gives 10 points even if you already knew the + * wand... */ + if (objects[obj->otyp].oc_name_known) + more_experienced(0, 10); +} + /* while loading bones, clean up text which might accidentally or maliciously disrupt player's terminal when displayed */ void From b4f57f0edcdbb018bcbbcfaca13d729eb8381def Mon Sep 17 00:00:00 2001 From: copperwater Date: Mon, 13 Jan 2020 21:40:34 -0500 Subject: [PATCH 10/36] Small refactor: split off most of back_to_glyph into back_to_defsym This is needed for the next commit, which needs a way of getting the string representation of a place on the map. back_to_glyph already did all the necessary work to get a defsym, but it immediately converted that into a glyph. So, extract this out into its own function. --- include/extern.h | 1 + src/display.c | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/extern.h b/include/extern.h index 7e4af0781d..de37837836 100644 --- a/include/extern.h +++ b/include/extern.h @@ -388,6 +388,7 @@ E void FDECL(row_refresh, (int, int, int)); E void NDECL(cls); E void FDECL(flush_screen, (int)); E int FDECL(back_to_glyph, (XCHAR_P, XCHAR_P)); +E int FDECL(back_to_defsym, (XCHAR_P, XCHAR_P)); E int FDECL(zapdir_to_glyph, (int, int, int)); E int FDECL(glyph_at, (XCHAR_P, XCHAR_P)); E void NDECL(reglyph_darkroom); diff --git a/src/display.c b/src/display.c index 0efe1582c9..637c9e2e4e 100644 --- a/src/display.c +++ b/src/display.c @@ -1744,6 +1744,15 @@ int cursor_on_u; int back_to_glyph(x, y) xchar x, y; +{ + return cmap_to_glyph(back_to_defsym(x, y)); +} + +/* extracted from back_to_glyph so that we can get the string description of a + * terrain spot on the map */ +int +back_to_defsym(x, y) +xchar x, y; { int idx; struct rm *ptr = &(levl[x][y]); @@ -1858,12 +1867,12 @@ xchar x, y; idx = (ptr->horizontal) ? S_hodbridge : S_vodbridge; break; default: - impossible("back_to_glyph: unknown level type [ = %d ]", ptr->typ); + impossible("back_to_defsym: unknown level type [ = %d ]", ptr->typ); idx = S_room; break; } - return cmap_to_glyph(idx); + return idx; } /* From 7ece5e86e46c27dbef55c63168f876a31bf828b1 Mon Sep 17 00:00:00 2001 From: copperwater Date: Sat, 18 Aug 2018 12:35:59 -0400 Subject: [PATCH 11/36] Monsters can hide under furniture and can move between hiding places Headstones, altars, sinks, ladders, and thrones all provide enough cover for a monster that can hide under objects, even if there are no objects there. They behave exactly the same; for instance, a concealable monster that steps onto a sink will vanish under it and become undetected. The AI for concealable monsters is tweaked so that in adjacent areas of concealing terrain such as a large area of scattered objects (a treasure zoo's gold piles, perhaps), they will pick their spot to move and *then* decide with their 90% chance not to move, *if* the spot would be moving out of concealment. Previously this was tested before they actually looked for new places to move, meaning that a monster able to move from one concealed spot to another would be pointlessly sluggish. --- include/display.h | 4 ++++ include/extern.h | 1 + src/apply.c | 9 ++++++--- src/dig.c | 2 +- src/insight.c | 2 ++ src/mon.c | 25 +++++++++++++++++-------- src/monmove.c | 33 +++++++++++++++++++++++++++++++-- src/pager.c | 14 ++++++++++---- src/polyself.c | 2 +- src/trap.c | 2 +- src/uhitm.c | 10 ++++++---- src/zap.c | 4 ++++ 12 files changed, 84 insertions(+), 24 deletions(-) diff --git a/include/display.h b/include/display.h index 721b7f10be..f3292a6f09 100644 --- a/include/display.h +++ b/include/display.h @@ -239,6 +239,10 @@ enum explosion_types { /* else U_AP_TYPE == M_AP_MONSTER */ \ : monnum_to_glyph(g.youmonst.mappearance))) +/* Get a string describing the terrain at (x, y). */ +#define explain_terrain(x, y) \ + (defsyms[back_to_defsym((x), (y))].explanation) + /* * A glyph is an abstraction that represents a _unique_ monster, object, * dungeon part, or effect. The uniqueness is important. For example, diff --git a/include/extern.h b/include/extern.h index de37837836..f4e03f0548 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1582,6 +1582,7 @@ E void FDECL(mon_yells, (struct monst *, const char *)); E int FDECL(dochug, (struct monst *)); E boolean FDECL(m_digweapon_check, (struct monst *, XCHAR_P, XCHAR_P)); E int FDECL(m_move, (struct monst *, int)); +E int FDECL(concealed_spot, (int, int)); E void FDECL(dissolve_bars, (int, int)); E boolean FDECL(closed_door, (int, int)); E boolean FDECL(accessible, (int, int)); diff --git a/src/apply.c b/src/apply.c index faef080bf3..0030906840 100644 --- a/src/apply.c +++ b/src/apply.c @@ -2127,9 +2127,12 @@ long timeout; suppress_see = TRUE; if (mtmp->mundetected) { - if (hides_under(mtmp->data) && mshelter) { - Sprintf(and_vanish, " and %s under %s", - locomotion(mtmp->data, "crawl"), doname(mshelter)); + if (hides_under(mtmp->data) && concealed_spot(mtmp->mx, mtmp->my)) { + Sprintf(and_vanish, " and %s under %s%s", + locomotion(mtmp->data, "crawl"), + (mshelter ? "" : "the "), + (mshelter ? doname(mshelter) : + explain_terrain(mtmp->mx, mtmp->my))); } else if (mtmp->data->mlet == S_MIMIC || mtmp->data->mlet == S_EEL) { suppress_see = TRUE; diff --git a/src/dig.c b/src/dig.c index 05369498f2..19a52778b1 100644 --- a/src/dig.c +++ b/src/dig.c @@ -2041,7 +2041,7 @@ long timeout; struct monst *mtmp = m_at(x, y); /* a hiding monster may be exposed */ - if (mtmp && !OBJ_AT(x, y) && mtmp->mundetected + if (mtmp && !concealed_spot(x, y) && mtmp->mundetected && hides_under(mtmp->data)) { mtmp->mundetected = 0; } else if (x == u.ux && y == u.uy && u.uundetected && hides_under(g.youmonst.data)) diff --git a/src/insight.c b/src/insight.c index 519a64be47..ed99514327 100644 --- a/src/insight.c +++ b/src/insight.c @@ -1757,6 +1757,8 @@ int msgflag; /* for variant message phrasing */ if (o) Sprintf(bp, " underneath %s", ansimpleoname(o)); + else if (concealed_spot(u.ux, u.uy)) + Sprintf(bp, " under %s", explain_terrain(u.ux, u.uy)); } else if (is_clinger(g.youmonst.data) || Flying) { /* Flying: 'lurker above' hides on ceiling but doesn't cling */ Sprintf(bp, " on the %s", ceiling(u.ux, u.uy)); diff --git a/src/mon.c b/src/mon.c index c90ddcb287..5342f20a5e 100644 --- a/src/mon.c +++ b/src/mon.c @@ -3422,7 +3422,8 @@ struct monst *mon; } } -/* unwatched hiders may hide again; if so, returns True */ +/* unwatched hiders may hide again; if so, returns True. + * Only applies to is_hider monsters, *not* hides_under monsters. */ static boolean restrap(mtmp) register struct monst *mtmp; @@ -3448,7 +3449,8 @@ register struct monst *mtmp; return FALSE; } -/* monster/hero tries to hide under something at the current location */ +/* monster/hero tries to hide under something at the current location. + * Only applies to hides_under monsters, *not* is_hider monsters. */ boolean hideunder(mtmp) struct monst *mtmp; @@ -3465,14 +3467,21 @@ struct monst *mtmp; ; /* can't hide while stuck in a non-pit trap */ } else if (mtmp->data->mlet == S_EEL) { undetected = (is_pool(x, y) && !Is_waterlevel(&u.uz)); - } else if (hides_under(mtmp->data) && OBJ_AT(x, y)) { - struct obj *otmp = g.level.objects[x][y]; + } else if (hides_under(mtmp->data)) { + int concealment = concealed_spot(x, y); - /* most monsters won't hide under cockatrice corpse */ - if (otmp->nexthere || otmp->otyp != CORPSE - || (mtmp == &g.youmonst ? Stone_resistance : resists_ston(mtmp)) - || !touch_petrifies(&mons[otmp->corpsenm])) + if (concealment == 2) { /* object cover */ + struct obj *otmp = g.level.objects[x][y]; + + /* most monsters won't hide under cockatrice corpse */ + if (otmp->nexthere || otmp->otyp != CORPSE + || (mtmp == &g.youmonst ? Stone_resistance : resists_ston(mtmp)) + || !touch_petrifies(&mons[otmp->corpsenm])) + undetected = TRUE; + } + else if (concealment == 1) { /* terrain cover, no objects */ undetected = TRUE; + } } if (is_u) diff --git a/src/monmove.c b/src/monmove.c index 8842f1b66d..736ec2f790 100644 --- a/src/monmove.c +++ b/src/monmove.c @@ -894,8 +894,6 @@ register int after; finish_meating(mtmp); return 3; /* still eating */ } - if (hides_under(ptr) && OBJ_AT(mtmp->mx, mtmp->my) && rn2(10)) - return 0; /* do not leave hiding place */ /* Where does 'mtmp' think you are? Not necessary if m_move() called from this file, but needed for other calls of m_move(). */ @@ -1316,6 +1314,13 @@ register int after; return 3; } + /* hiding-under monsters will attack things from their hiding spot but + * are less likely to venture out */ + if (hides_under(ptr) && concealed_spot(mtmp->mx, mtmp->my) + && !concealed_spot(nix, niy) && rn2(10)) { + return 0; + } + if ((info[chi] & ALLOW_MDISP)) { struct monst *mtmp2; int mstatus; @@ -1606,6 +1611,30 @@ register int after; return mmoved; } +/* Return 1 or 2 if a hides_under monster can conceal itself at this spot. + * If the monster can hide under an object, return 2. + * Otherwise, monsters can hide in grass and under some types of dungeon + * furniture. If no object is available but the terrain is suitable, return 1. + * Return 0 if the monster can't conceal itself. + */ +int +concealed_spot(x, y) +register int x, y; +{ + if (OBJ_AT(x, y)) + return 2; + switch (levl[x][y].typ) { + case SINK: + case ALTAR: + case THRONE: + case LADDER: + case GRAVE: + return 1; + default: + return 0; + } +} + void dissolve_bars(x, y) register int x, y; diff --git a/src/pager.c b/src/pager.c index 7a6acebd36..db355ef0fd 100644 --- a/src/pager.c +++ b/src/pager.c @@ -147,10 +147,16 @@ char *outbuf; Strcpy(outbuf, ", hiding"); if (hides_under(mon->data)) { Strcat(outbuf, " under "); - /* remembered glyph, not glyph_at() which is 'mon' */ - if (glyph_is_object(glyph)) - goto objfrommap; - Strcat(outbuf, something); + int hidetyp = concealed_spot(x, y); + if (hidetyp == 1) { /* hiding with terrain */ + Strcat(outbuf, explain_terrain(x, y)); + } + else { + /* remembered glyph, not glyph_at() which is 'mon' */ + if (glyph_is_object(glyph)) + goto objfrommap; + Strcat(outbuf, something); + } } else if (is_hider(mon->data)) { Sprintf(eos(outbuf), " on the %s", ceiling_hider(mon->data) ? "ceiling" diff --git a/src/polyself.c b/src/polyself.c index 5beef979f6..08310b744f 100644 --- a/src/polyself.c +++ b/src/polyself.c @@ -1531,7 +1531,7 @@ dohide() u.uundetected = 0; return 0; } - if (hides_under(g.youmonst.data) && !g.level.objects[u.ux][u.uy]) { + if (hides_under(g.youmonst.data) && !concealed_spot(u.ux, u.uy)) { There("is nothing to hide under here."); u.uundetected = 0; return 0; diff --git a/src/trap.c b/src/trap.c index 7aa3f3e71e..c06471071c 100644 --- a/src/trap.c +++ b/src/trap.c @@ -732,7 +732,7 @@ int *fail_reason; /* avoid hiding under nothing */ if (x == u.ux && y == u.uy && Upolyd && hides_under(g.youmonst.data) - && !OBJ_AT(x, y)) + && !concealed_spot(x, y)) u.uundetected = 0; if (fail_reason) diff --git a/src/uhitm.c b/src/uhitm.c index 53a82d7d3d..1a679811a4 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -181,16 +181,18 @@ struct obj *wep; /* uwep for attack(), null for kick_monster() */ return FALSE; } if (!((Blind ? Blind_telepat : Unblind_telepat) || Detect_monsters)) { - struct obj *obj; if (!Blind && Hallucination) pline("A %s %s appeared!", mtmp->mtame ? "tame" : "wild", l_monnam(mtmp)); else if (Blind || (is_pool(mtmp->mx, mtmp->my) && !Underwater)) pline("Wait! There's a hidden monster there!"); - else if ((obj = g.level.objects[mtmp->mx][mtmp->my]) != 0) - pline("Wait! There's %s hiding under %s!", - an(l_monnam(mtmp)), doname(obj)); + else if (concealed_spot(mtmp->mx, mtmp->my)) { + struct obj *obj = g.level.objects[mtmp->mx][mtmp->my]; + pline("Wait! There's %s hiding under %s%s!", an(l_monnam(mtmp)), + obj ? "" : "the ", + obj ? doname(obj) : explain_terrain(mtmp->mx, mtmp->my)); + } return TRUE; } } diff --git a/src/zap.c b/src/zap.c index 927e409c8f..1ca29dadfd 100644 --- a/src/zap.c +++ b/src/zap.c @@ -1976,6 +1976,10 @@ struct obj *obj, *otmp; (void) boxlock(obj, otmp); if (obj_shudders(obj)) { + /* Do we need to possibly refresh our cover state? + * If we are currently hiding and the shuddering object might + * have been the only thing on our square, call hideunder + * again. */ boolean cover = ((obj == g.level.objects[u.ux][u.uy]) && u.uundetected && hides_under(g.youmonst.data)); From 7c921818b119bd20de2b23347265dd0633600c81 Mon Sep 17 00:00:00 2001 From: copperwater Date: Mon, 7 May 2018 20:32:26 -0400 Subject: [PATCH 12/36] Implement monster introductions from SpliceHack Nymphs and foocubi (or more specifically anything with AD_SEDU and AD_SSEX attacks) will now call mintroduce() when they first engage you with one of these attacks, which causes the monster to introduce itself to you with a randomly chosen name (naming itself in the process). The name lists are different for nymphs, male demons, and female demons, and are not the same as in SpliceHack (which uses human names, which come off pretty bland when you encounter them). Nymphs won't introduce if they go through the "bragging about loot" routine; they'll only do it when they at least attempt to charm you. --- include/extern.h | 1 + src/do_name.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ src/mhitu.c | 29 ++++++++++++++---------- 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/include/extern.h b/include/extern.h index f4e03f0548..37f102b580 100644 --- a/include/extern.h +++ b/include/extern.h @@ -480,6 +480,7 @@ E struct monst *FDECL(christen_orc, (struct monst *, const char *, const char *)); E const char *FDECL(noveltitle, (int *)); E const char *FDECL(lookup_novel, (const char *, int *)); +E void FDECL(mintroduce, (struct monst *)); /* ### do_wear.c ### */ diff --git a/src/do_name.c b/src/do_name.c index 2725f4955a..096dc1ffc9 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -2333,4 +2333,62 @@ int *idx; return (const char *) 0; } +/* Most of these are actual names of nymphs from mythology. */ +const char* nymphnames[] = { + "Erythea", "Hesperia", "Arethusa", "Pasithea", "Thaleia", "Halimede", + "Actaea", "Electra", "Maia", "Nesaea", "Alcyone", "Asterope", + "Callianeira", "Nausithoe", "Dione", "Thetis", "Ephyra", "Eulimene", + "Nerea", "Laomedeia", "Echo", "Maera", "Eurydice", "Lysianassa", "Phoebe", + "Daphnis", "Daphnae", "Melinoe", "Othreis", "Polychrome" +}; + +const char* maldemonnames[] = { + "Agiel", "Kali", "Amon", "Foras", "Armaros", "Orias", "Malthus", "Asag", + "Raum", "Iblis", "Vanth", "Bael", "Leonard", "Barbas", "Charun", "Ishmael", + "Balthamel", "Rahvin" +}; + +const char* femdemonnames[] = { + "Mara", "Lamia", "Meraxes", "Daeva", "Amy", "Lilith", "Aliss", "Berith", + "Euryale", "Zorya", "Rhaenyra", "Bellatrix", "Rusalka", "Messaana", + "Jadis", "Anzu", "Eve", "Bilquis", "Cyndane", "Vanessa", "Daenera" +}; +#define rnd_name(list) list[rn2(SIZE(list))] + +/* Monster introduces themselves. If they're not currently named, give them a + * random name from the specified list. */ +void +mintroduce(mtmp) +struct monst *mtmp; +{ + if (!has_mname(mtmp)) { + const char* name; + if (mtmp->data->mlet == S_NYMPH) { + name = rnd_name(nymphnames); + } + else if (is_demon(mtmp->data)) { + if (mtmp->female) + name = rnd_name(femdemonnames); + else + name = rnd_name(maldemonnames); + } + else { + impossible("mintroduce: monster type %s has no defined names!", + mtmp->data->mname); + return; + } + const char* pronoun = + genders[is_neuter(mtmp->data) ? 2 : mtmp->female].him; + if (!Deaf) { + pline("%s introduces %sself to you as %s.", Monnam(mtmp), pronoun, + name); + christen_monst(mtmp, name); + } else { + pline("%s seems to be introducing %sself, but you can't hear %s.", + Monnam(mtmp), pronoun, pronoun); + } + } + return; +} + /*do_name.c*/ diff --git a/src/mhitu.c b/src/mhitu.c index f28f0c6b73..88730b4f11 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -1380,9 +1380,11 @@ register struct attack *mattk; case AD_SSEX: if (SYSOPT_SEDUCE) { - if (could_seduce(mtmp, &g.youmonst, mattk) == 1 && !mtmp->mcan) + if (could_seduce(mtmp, &g.youmonst, mattk) == 1 && !mtmp->mcan) { + mintroduce(mtmp); if (doseduce(mtmp)) return 3; + } break; } /*FALLTHRU*/ @@ -1406,18 +1408,21 @@ register struct attack *mattk; if (!tele_restrict(mtmp)) (void) rloc(mtmp, TRUE); return 3; - } else if (mtmp->mcan) { - if (!Blind) - pline("%s tries to %s you, but you seem %s.", - Adjmonnam(mtmp, "plain"), - flags.female ? "charm" : "seduce", - flags.female ? "unaffected" : "uninterested"); - if (rn2(3)) { - if (!tele_restrict(mtmp)) - (void) rloc(mtmp, TRUE); - return 3; + } else { + mintroduce(mtmp); + if (mtmp->mcan) { + if (!Blind) + pline("%s tries to %s you, but you seem %s.", + Adjmonnam(mtmp, "plain"), + flags.female ? "charm" : "seduce", + flags.female ? "unaffected" : "uninterested"); + if (rn2(3)) { + if (!tele_restrict(mtmp)) + (void) rloc(mtmp, TRUE); + return 3; + } + break; } - break; } buf[0] = '\0'; switch (steal(mtmp, buf)) { From 9b9b22fcf5f61313ccdf8d1a0bcbbe93f7c4449c Mon Sep 17 00:00:00 2001 From: copperwater Date: Mon, 6 Nov 2017 11:13:56 -0500 Subject: [PATCH 13/36] Replace Mercury with Apollo and Venus with Diana in the Ranger pantheon Minor change to remove the conflict between Mercury and Hermes, who already exists in the Healer pantheon. Plus, neither Mercury nor Venus are actually gods of archery (Mars isn't either but being a war god he's acceptably close), so Apollo and Diana make much more sense. --- src/role.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/role.c b/src/role.c index 98854f3dc8..47b74b215c 100644 --- a/src/role.c +++ b/src/role.c @@ -362,19 +362,6 @@ const struct Role roles[NUM_ROLES+1] = { -4 }, { { "Ranger", 0 }, { -#if 0 /* OBSOLETE */ - {"Edhel", "Elleth"}, - {"Edhel", "Elleth"}, /* elf-maid */ - {"Ohtar", "Ohtie"}, /* warrior */ - {"Kano", "Kanie"}, /* commander (Q.) ['a] educated guess, - until further research- SAC */ - {"Arandur"," Aranduriel"}, /* king's servant, minister (Q.) - guess */ - {"Hir", "Hiril"}, /* lord, lady (S.) ['ir] */ - {"Aredhel", "Arwen"}, /* noble elf, maiden (S.) */ - {"Ernil", "Elentariel"}, /* prince (S.), elf-maiden (Q.) */ - {"Elentar", "Elentari"}, /* Star-king, -queen (Q.) */ - "Solonor Thelandira", "Aerdrie Faenya", "Lolth", /* Elven */ -#endif { "Tenderfoot", 0 }, { "Lookout", 0 }, { "Trailblazer", 0 }, @@ -384,7 +371,7 @@ const struct Role roles[NUM_ROLES+1] = { { "Archer", 0 }, { "Sharpshooter", 0 }, { "Marksman", "Markswoman" } }, - "Mercury", "_Venus", "Mars", /* Roman/planets */ + "Apollo", "_Diana", "Mars", /* Roman/planets */ "Ran", "Orion's camp", "the cave of the wumpus", From 1fc4e7d4567cc5d84ea07cd7f43edc81fcd97e6e Mon Sep 17 00:00:00 2001 From: copperwater Date: Mon, 13 Jan 2020 22:23:41 -0500 Subject: [PATCH 14/36] Grant a newt-like energy buzz from spellcasting monsters' corpses Spellcasting monsters are highly magical and so it seems reasonable that you can tap into some of that magic when you eat them. Newts still grant d3 energy, whereas casters give d(baselevel). The odds of gaining a point of maximum energy are not changed - still 1/3 of the time and only when you would have gone over maximum. Also, change the message to a "moderate" buzz instead of mild when you do gain maximum energy by this mechanism. --- src/eat.c | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/eat.c b/src/eat.c index 17040fc9e2..f09a338e76 100644 --- a/src/eat.c +++ b/src/eat.c @@ -952,23 +952,6 @@ int pm; (void) eatmdone(); switch (pm) { - case PM_NEWT: - /* MRKR: "eye of newt" may give small magical energy boost */ - if (rn2(3) || 3 * u.uen <= 2 * u.uenmax) { - int old_uen = u.uen; - - u.uen += rnd(3); - if (u.uen > u.uenmax) { - if (!rn2(3)) - u.uenmax++; - u.uen = u.uenmax; - } - if (old_uen != u.uen) { - You_feel("a mild buzz."); - g.context.botl = 1; - } - } - break; case PM_WRAITH: pluslvl(FALSE); break; @@ -1112,6 +1095,10 @@ int pm; if (check_intrinsics) { struct permonst *ptr = &mons[pm]; boolean conveys_STR = is_giant(ptr); + /* MRKR: "eye of newt" may give small magical energy boost + * as well as other magical monsters */ + boolean conveys_energy = (attacktype(&mons[pm], AT_MAGC) + || pm == PM_NEWT); int i, count; if (dmgtype(ptr, AD_STUN) || dmgtype(ptr, AD_HALU) @@ -1135,6 +1122,11 @@ int pm; tmp = -1; /* use -1 as fake prop index for STR */ debugpline1("\"Intrinsic\" strength, %d", tmp); } + else if (conveys_energy) { + count = 1; + tmp = -2; + debugpline1("\"Intrinsic\" energy gain, %d", tmp); + } for (i = 1; i <= LAST_PROP; i++) { if (!intrinsic_possible(i, ptr)) continue; @@ -1154,6 +1146,23 @@ int pm; /* if something was chosen, give it now (givit() might fail) */ if (tmp == -1) gainstr((struct obj *) 0, 0, TRUE); + else if (tmp == -2) { + if (rn2(3) || 3 * u.uen <= 2 * u.uenmax) { + int old_uen = u.uen, old_uenmax = u.uenmax; + u.uen += (pm == PM_NEWT) ? rnd(3) : rnd(mons[pm].mlevel); + if (u.uen > u.uenmax) { + if (!rn2(3)) + u.uenmax++; + u.uen = u.uenmax; + } + if (old_uen != u.uen) { + You_feel("a %s buzz.", + old_uenmax != u.uenmax ? "moderate" : "mild"); + g.context.botl = 1; + } + } + + } else if (tmp > 0) givit(tmp, ptr); } /* check_intrinsics */ From c497bc9c8b5e93a8d58ec0c51ef019e41e459253 Mon Sep 17 00:00:00 2001 From: copperwater Date: Tue, 31 Oct 2017 22:26:13 -0400 Subject: [PATCH 15/36] Green slime now has an engulfing sliming attack Green slimes possess a unique way to threaten the player, but by the time they appear, the player is likely to shrug off most of their sliming attacks due to high magic cancellation from their armor, making it easy to fix the occasional one that gets through. Giving them an engulfing attack which always starts the sliming process makes them more of a threat. Some edge cases: - If you are fiery, you will burn through and instakill the slime. - If you have unchanging, are noncorporeal, are a green slime, or the slime engulfing you is cancelled, you still get engulfed, but are unaffected. --- src/mhitu.c | 27 ++++++++++++++++++++++++++- src/monst.c | 4 ++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/mhitu.c b/src/mhitu.c index 88730b4f11..533c1d3f93 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -1851,6 +1851,7 @@ struct attack *mattk; struct obj *otmp2; int i; boolean physical_damage = FALSE; + boolean mon_killed = FALSE; if (!u.uswallow) { /* swallows you */ int omx = mtmp->mx, omy = mtmp->my; @@ -2064,6 +2065,29 @@ struct attack *mattk; drain_en(tmp); tmp = 0; break; + case AD_SLIM: + /* engulf attack always hits regardless of magic cancellation */ + if (mtmp->mcan) { + /* still engulfed, but don't get slimed */ + break; + } + if (flaming(g.youmonst.data)) { + pline("Your body burns through %s!", mon_nam(mtmp)); + /* kills the slime from the inside out - give experience but don't + * break pacifist because this wasn't an active kill */ + xkilled(mtmp, XKILL_NOMSG | XKILL_NOCORPSE | XKILL_NOCONDUCT); + mon_killed = TRUE; + } else if (Unchanging || noncorporeal(g.youmonst.data) + || g.youmonst.data == &mons[PM_GREEN_SLIME]) { + You("are covered in slime, but seem unaffected."); + } else if (!Slimed) { + You("don't feel very well."); + make_slimed(10L, (char *) 0); + delayed_killer(SLIMED, KILLED_BY_AN, mtmp->data->mname); + } else { + pline("Yuck!"); + } + break; default: physical_damage = TRUE; tmp = 0; @@ -2083,7 +2107,8 @@ struct attack *mattk; pline("%s very hurriedly %s you!", Monnam(mtmp), is_animal(mtmp->data) ? "regurgitates" : "expels"); expels(mtmp, mtmp->data, FALSE); - } else if (!u.uswldtim || g.youmonst.data->msize >= MZ_HUGE) { + } else if (!mon_killed + && (!u.uswldtim || g.youmonst.data->msize >= MZ_HUGE)) { /* As of 3.6.2: u.uswldtim used to be set to 0 by life-saving but it expels now so the !u.uswldtim case is no longer possible; however, polymorphing into a huge form while already diff --git a/src/monst.c b/src/monst.c index 566f2fccf3..143049953b 100644 --- a/src/monst.c +++ b/src/monst.c @@ -1724,8 +1724,8 @@ struct permonst _mons2[] = { M2_HOSTILE | M2_NEUTER, 0, 6, CLR_BROWN), MON("green slime", S_PUDDING, LVL(6, 6, 6, 0, 0), (G_HELL | G_GENO | G_NOCORPSE | 1), - A(ATTK(AT_TUCH, AD_SLIM, 1, 4), ATTK(AT_NONE, AD_SLIM, 0, 0), NO_ATTK, - NO_ATTK, NO_ATTK, NO_ATTK), + A(ATTK(AT_TUCH, AD_SLIM, 1, 4), ATTK(AT_ENGL, AD_SLIM, 1, 1), + ATTK(AT_NONE, AD_SLIM, 0, 0), NO_ATTK, NO_ATTK, NO_ATTK), SIZ(400, 150, MS_SILENT, MZ_LARGE), MR_COLD | MR_ELEC | MR_POISON | MR_ACID | MR_STONE, 0, M1_BREATHLESS | M1_AMORPHOUS | M1_NOEYES | M1_NOLIMBS | M1_NOHEAD From b3162515aeb20cd87a3c41f7936b75a58246b67f Mon Sep 17 00:00:00 2001 From: copperwater Date: Thu, 28 Feb 2019 13:23:20 -0500 Subject: [PATCH 16/36] Add the Free Fortune Cookie Patch, by Nephi Small patch that creates a free fortune cookie upon eating a szechuan tin of something, half of the time. You don't get a cookie if you choose not to eat it and discard the open tin (I considered moving the cookie generation to before you smell the contents, but that would give away that it is szechuan). I did move it after the cprefx/cpostfx and greasy-finger calls, because when testing the original code I got "You consume szechuan newt. There is a free fortune cookie inside! l - a fortune cookie. You feel a mild buzz." which seems out of order. If the tin is unpaid, the cookie does not add any extra cost; this is because the tin containing the cookie was already being sold for its usual cost. It's not really abusable either, since the cookie sells for less than the tin does. --- src/eat.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/eat.c b/src/eat.c index f09a338e76..860fb196e7 100644 --- a/src/eat.c +++ b/src/eat.c @@ -1402,6 +1402,13 @@ const char *mesg; tintxts[r].txt, fingers_or_gloves(TRUE)); } + if (!strcmp(tintxts[r].txt, "szechuan") && rn2(2)) { + struct obj* cookie = mksobj(FORTUNE_COOKIE, FALSE, FALSE); + cookie->blessed = tin->blessed; + cookie->cursed = tin->cursed; + pline("There is a free fortune cookie inside!"); + hold_another_object(cookie, "It falls to the floor.", NULL, NULL); + } } else { /* spinach... */ if (tin->cursed) { pline("It contains some decaying%s%s substance.", From 48ea2a1b49c228338572f1251ef0e88e8b7a52af Mon Sep 17 00:00:00 2001 From: copperwater Date: Mon, 27 Nov 2017 23:35:42 -0500 Subject: [PATCH 17/36] Orc captains are lords and have speed 9 Minor fixes for them: their low speed doesn't make much sense considering other orcs are already speed 9, and they now count as lords to their kind (no other o monster is really a candidate for this). --- src/monst.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/monst.c b/src/monst.c index 143049953b..dd236fbde8 100644 --- a/src/monst.c +++ b/src/monst.c @@ -666,12 +666,12 @@ NEARDATA struct permonst mons_init[] = { M1_HUMANOID | M1_OMNIVORE, M2_ORC | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_MAGIC, M3_INFRAVISIBLE | M3_INFRAVISION, 5, HI_ZAP), - MON("orc-captain", S_ORC, LVL(5, 5, 10, 0, -5), (G_GENO | 1), + MON("orc-captain", S_ORC, LVL(5, 9, 10, 0, -5), (G_GENO | 1), A(ATTK(AT_WEAP, AD_PHYS, 2, 4), ATTK(AT_WEAP, AD_PHYS, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), SIZ(1350, 350, MS_ORC, MZ_HUMAN), MR_POISON, 0, M1_HUMANOID | M1_OMNIVORE, - M2_ORC | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, + M2_ORC | M2_LORD | M2_STRONG | M2_GREEDY | M2_JEWELS | M2_COLLECT, M3_INFRAVISIBLE | M3_INFRAVISION, 7, HI_LORD), /* * piercers From 5bf63aee7dc32afbd5343d86879fdbfe92bc14ce Mon Sep 17 00:00:00 2001 From: copperwater Date: Sat, 24 Feb 2018 13:00:13 -0500 Subject: [PATCH 18/36] Change mechanics of rings dropped in sinks * Rings dropped down a sink now have an 80% chance of becoming buried under the sink, rather than 20%. This is to make it more attractive to try and identify a ring with a sink before waiting to find a duplicate, which usually takes a long time. * All sinks (except those in special levels, because the special level code is so weird I have no idea why I couldn't get it to work) generate with a ring pre-buried. The "ring looted from sink" flag is removed. The special level exception is not too important right now; the only sink currently on a special level is in Bazaar Town. * Kicking a sink that has rings buried under it and getting the "Flupp!" effect, or drinking from a sink and getting the ring finding effect, will cause one of the buried rings to be spit out, providing a way to access them without destroying the sink. Because this deletes S_LRING, it also deletes a line which handles that in nhlua.c. --- include/extern.h | 1 + include/rm.h | 1 - src/do.c | 2 +- src/dokick.c | 6 ++---- src/fountain.c | 29 +++++++++++++++++++++++++---- src/mklev.c | 6 ++++++ src/nhlua.c | 2 -- 7 files changed, 35 insertions(+), 12 deletions(-) diff --git a/include/extern.h b/include/extern.h index 37f102b580..fc3f78199c 100644 --- a/include/extern.h +++ b/include/extern.h @@ -864,6 +864,7 @@ E void NDECL(drinkfountain); E void FDECL(dipfountain, (struct obj *)); E void FDECL(breaksink, (int, int)); E void NDECL(drinksink); +E struct obj* FDECL(ring_from_sink, (XCHAR_P, XCHAR_P)); /* ### hack.c ### */ diff --git a/include/rm.h b/include/rm.h index 761e8b30cb..8e651ac284 100644 --- a/include/rm.h +++ b/include/rm.h @@ -375,7 +375,6 @@ extern const struct symdef def_warnsyms[WARNCOUNT]; */ #define S_LPUDDING 1 #define S_LDWASHER 2 -#define S_LRING 4 /* * The four directions for a DrawBridge. diff --git a/src/do.c b/src/do.c index 2e5301d1b3..f798642e6d 100644 --- a/src/do.c +++ b/src/do.c @@ -593,7 +593,7 @@ register struct obj *obj; pline_The("sink backs up, leaving %s.", doname(obj)); obj->in_use = FALSE; dropx(obj); - } else if (!rn2(5)) { + } else if (rn2(5)) { freeinv(obj); obj->in_use = FALSE; obj->ox = u.ux; diff --git a/src/dokick.c b/src/dokick.c index 5852d43245..8e3b50d995 100644 --- a/src/dokick.c +++ b/src/dokick.c @@ -1194,14 +1194,12 @@ dokick() : !Deaf ? "You hear a sloshing sound" /* Deaf-aware */ : "Something splashes you in the", buf); - if (!(g.maploc->looted & S_LRING)) { /* once per sink */ + struct obj * otmp = ring_from_sink(x, y); + if (otmp) { if (!Blind) You_see("a ring shining in its midst."); - (void) mkobj_at(RING_CLASS, x, y, TRUE); - newsym(x, y); exercise(A_DEX, TRUE); exercise(A_WIS, TRUE); /* a discovery! */ - g.maploc->looted |= S_LRING; } return 1; } diff --git a/src/fountain.c b/src/fountain.c index f5be392dd0..bc99247a55 100644 --- a/src/fountain.c +++ b/src/fountain.c @@ -585,12 +585,11 @@ drinksink() obfree(otmp, (struct obj *) 0); break; case 5: - if (!(levl[u.ux][u.uy].looted & S_LRING)) { + /* look for a ring buried beneath the sink ("in the pipe") */ + otmp = ring_from_sink(u.ux, u.uy); + if (otmp) { You("find a ring in the sink!"); - (void) mkobj_at(RING_CLASS, u.ux, u.uy, TRUE); - levl[u.ux][u.uy].looted |= S_LRING; exercise(A_WIS, TRUE); - newsym(u.ux, u.uy); } else pline("Some dirty %s backs up in the drain.", hliquid("water")); break; @@ -640,4 +639,26 @@ drinksink() } } +/* If there is a ring buried under a sink, pop it out and place it on the sink. + * Return the ring that appeared or NULL if there was no ring there. + * The caller should handle messages. */ +struct obj * +ring_from_sink(sink_x, sink_y) +xchar sink_x, sink_y; +{ + struct obj* otmp; + for (otmp = g.level.buriedobjlist; otmp; otmp = otmp->nobj) { + if (otmp->ox == sink_x && otmp->oy == sink_y + && otmp->oclass == RING_CLASS) { + break; + } + } + if (otmp) { + obj_extract_self(otmp); + place_object(otmp, sink_x, sink_y); + newsym(sink_x, sink_y); + } + return otmp; +} + /*fountain.c*/ diff --git a/src/mklev.c b/src/mklev.c index 07feca909c..79a4f87595 100644 --- a/src/mklev.c +++ b/src/mklev.c @@ -1777,6 +1777,12 @@ struct mkroom *croom; /* Put a sink at m.x, m.y */ levl[m.x][m.y].typ = SINK; + /* All sinks have a ring stuck in the pipes below */ + struct obj* ring = mkobj(RING_CLASS, TRUE); + ring->ox = m.x; + ring->oy = m.y; + add_to_buried(ring); + g.level.flags.nsinks++; } diff --git a/src/nhlua.c b/src/nhlua.c index b3906592bf..3df8d56edd 100644 --- a/src/nhlua.c +++ b/src/nhlua.c @@ -386,8 +386,6 @@ lua_State *L; (levl[x][y].flags & S_LPUDDING)); nhl_add_table_entry_bool(L, "dishwasher", (levl[x][y].flags & S_LDWASHER)); - nhl_add_table_entry_bool(L, "ring", - (levl[x][y].flags & S_LRING)); } /* TODO: drawbridges, walls, ladders, room=>ICED_xxx */ From a54abc28695cd3a10effe875c465eaf668dcc702 Mon Sep 17 00:00:00 2001 From: copperwater Date: Sun, 29 Oct 2017 17:31:00 -0400 Subject: [PATCH 19/36] Buff the effects of the scroll of light Uncursed light now gives radius 11 light (in line-of-sight, the same as any other radius effect), and blessed light now illuminates the entire level. The intention of this change is for the scroll of light not to be useless like it currently is. With the current light rules, the scroll is inferior to both the wand of light or the spell of light; hopefully full-level, non-line-of-sight lighting will have good uses. This also accounts for lighting up a gremlin's square from anywhere on the level (suppressing its cry of pain if it's not in sight and angering it if it was the player's turn). --- src/read.c | 33 ++++++++++++++++++++++++++++----- src/uhitm.c | 15 +++++++++++---- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/read.c b/src/read.c index c036147da7..1234a611c8 100644 --- a/src/read.c +++ b/src/read.c @@ -1923,11 +1923,34 @@ struct obj *obj; g.rooms[rnum].rlit = on; } /* hallways remain dark on the rogue level */ - } else - do_clear_area(u.ux, u.uy, - (obj && obj->oclass == SCROLL_CLASS && obj->blessed) - ? 9 : 5, - set_lit, (genericptr_t) (on ? &is_lit : (char *) 0)); + } else { + int radius; + if(obj && obj->oclass == SCROLL_CLASS) { + /* blessed scroll lights up entire level */ + if(obj->blessed) { + int x, y; + radius = -1; + for (x = 1; x < COLNO; x++) { + for (y = 1; y < ROWNO; y++) { + set_lit(x, y, (on ? &is_lit : NULL)); + } + } + } + else { + /* uncursed gets a much larger area than the + * 5 it had previously */ + radius = 11; + } + } + else { + radius = 5; + } + + if(radius > 0) { + do_clear_area(u.ux, u.uy, radius, set_lit, + (genericptr_t) (on ? &is_lit : (char *) 0)); + } + } /* * If we are not blind, then force a redraw on all positions in sight diff --git a/src/uhitm.c b/src/uhitm.c index 1a679811a4..0c901d0676 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -3254,8 +3254,10 @@ light_hits_gremlin(mon, dmg) struct monst *mon; int dmg; { - pline("%s %s!", Monnam(mon), - (dmg > mon->mhp / 2) ? "wails in agony" : "cries out in pain"); + if (canspotmon(mon)) { + pline("%s %s!", Monnam(mon), + (dmg > mon->mhp / 2) ? "wails in agony" : "cries out in pain"); + } mon->mhp -= dmg; wake_nearto(mon->mx, mon->my, 30); if (DEADMONSTER(mon)) { @@ -3263,8 +3265,13 @@ int dmg; monkilled(mon, (char *) 0, AD_BLND); else killed(mon); - } else if (cansee(mon->mx, mon->my) && !canspotmon(mon)) { - map_invisible(mon->mx, mon->my); + } else { + if (cansee(mon->mx, mon->my) && !canspotmon(mon)) { + map_invisible(mon->mx, mon->my); + } + if (!g.context.mon_moving) { + setmangry(mon, FALSE); + } } } From 34c1acb7ce7d12fad5ed5264388545209ece32fd Mon Sep 17 00:00:00 2001 From: copperwater Date: Tue, 14 Jan 2020 21:43:57 -0500 Subject: [PATCH 20/36] Change the color of floating eyes to cyan It is an extremely common complaint that floating eyes' dark blue coloration is so dark that players don't see them in time and bumble into them. We can't make players reconfigure their color palettes, but we can change the eye's color to something that is naturally more visible. --- src/monst.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monst.c b/src/monst.c index dd236fbde8..b9a776a032 100644 --- a/src/monst.c +++ b/src/monst.c @@ -310,7 +310,7 @@ NEARDATA struct permonst mons_init[] = { NO_ATTK), SIZ(10, 10, MS_SILENT, MZ_SMALL), 0, 0, M1_FLY | M1_AMPHIBIOUS | M1_NOLIMBS | M1_NOHEAD | M1_NOTAKE, - M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 3, CLR_BLUE), + M2_HOSTILE | M2_NEUTER, M3_INFRAVISIBLE, 3, CLR_CYAN), MON("freezing sphere", S_EYE, LVL(6, 13, 4, 0, 0), (G_NOCORPSE | G_NOHELL | G_GENO | 2), A(ATTK(AT_EXPL, AD_COLD, 4, 6), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, From ed082b4f74544c0787d782f5cd4486834260fb86 Mon Sep 17 00:00:00 2001 From: copperwater Date: Sun, 1 Apr 2018 07:40:46 -0400 Subject: [PATCH 21/36] Polymorph traps disappear when they successfully polymorph something This was inconsistent; the trap disappeared when the player set it off but not when a monster did, which enabled players to pursue strategies like repeatedly displacing a pet onto a polymorph trap while protected themselves via magic resistance. However, that's not the primary motivation for this change; the main reason is so that a crowd of monsters can't all polymorph repeatedly into too-strong forms. Also so that the player is in general less likely to run into a polymorph trap in an area that monsters have obviously passed through. --- src/trap.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/trap.c b/src/trap.c index c06471071c..8cb0e79414 100644 --- a/src/trap.c +++ b/src/trap.c @@ -1650,6 +1650,8 @@ struct obj *otmp; steedhit = TRUE; break; case POLY_TRAP: + deltrap(trap); + newsym(steed->mx, steed->my); if (!resists_magm(steed) && !resist(steed, WAND_CLASS, 0, NOTELL)) { struct permonst *mdat = steed->data; @@ -2683,11 +2685,14 @@ register struct monst *mtmp; if (resists_magm(mtmp)) { shieldeff(mtmp->mx, mtmp->my); } else if (!resist(mtmp, WAND_CLASS, 0, NOTELL)) { - if (newcham(mtmp, (struct permonst *) 0, FALSE, FALSE)) + if (newcham(mtmp, (struct permonst *) 0, FALSE, TRUE)) { /* we're done with mptr but keep it up to date */ mptr = mtmp->data; - if (in_sight) + deltrap(trap); + } + if (in_sight) { seetrap(trap); + } } break; case ROLLING_BOULDER_TRAP: From b8280687203e2f6dea41e982daeb264953287867 Mon Sep 17 00:00:00 2001 From: copperwater Date: Wed, 30 May 2018 21:15:11 -0400 Subject: [PATCH 22/36] Remove ability to wish for a random item via Escape or empty buffer I don't think anyone actually uses the "feature" where specifying nothing to wish for generates a random object. Also, it's terribly unintuitive that hitting Escape (or any key, like the arrow keys, which begins an escape sequence, or accidentally hitting Enter with the buffer still empty) resolves the wish, breaks wishless conduct, and gives the player an item they almost certainly don't want. Instead, wishing for ESC or "" is interpreted as resolving to a wish for "nothing", but the game will prompt the player to confirm that they really meant that. --- src/zap.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/zap.c b/src/zap.c index 1ca29dadfd..4d03a6a96b 100644 --- a/src/zap.c +++ b/src/zap.c @@ -5429,6 +5429,13 @@ makewish() buf[0] = '\0'; /* for EDIT_GETLIN */ goto retry; } + if (buf[0] == '\0') { + if (yn("Really forfeit this wish?") == 'y') { + Strcpy(buf, "nothing"); + } + else + goto retry; + } /* * Note: if they wished for and got a non-object successfully, * otmp == &zeroobj. That includes an artifact which has been denied. From ab1a736a6d6a8d29e4ea46bb28290c0983d694fb Mon Sep 17 00:00:00 2001 From: copperwater Date: Mon, 4 Jun 2018 18:51:55 -0400 Subject: [PATCH 23/36] Wearing a ring of regeneration makes it known and prints a message It's easy enough to test for anyway (by damaging yourself and seeing if you regenerate a point of HP every turn), so avoid encouraging that rather silly strategy and make it unambiguous. --- src/do_wear.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/do_wear.c b/src/do_wear.c index 3d20fe61f5..cdd9352ab8 100644 --- a/src/do_wear.c +++ b/src/do_wear.c @@ -947,7 +947,6 @@ register struct obj *obj; switch (obj->otyp) { case RIN_TELEPORTATION: - case RIN_REGENERATION: case RIN_SEARCHING: case RIN_HUNGER: case RIN_AGGRAVATE_MONSTER: @@ -964,6 +963,10 @@ register struct obj *obj; case RIN_SUSTAIN_ABILITY: case MEAT_RING: break; + case RIN_REGENERATION: + You_feel("invigorated!"); + learnring(obj, TRUE); + break; case RIN_STEALTH: toggle_stealth(obj, oldprop, TRUE); break; @@ -1059,7 +1062,6 @@ boolean gone; switch (obj->otyp) { case RIN_TELEPORTATION: - case RIN_REGENERATION: case RIN_SEARCHING: case RIN_HUNGER: case RIN_AGGRAVATE_MONSTER: @@ -1076,6 +1078,10 @@ boolean gone; case RIN_SUSTAIN_ABILITY: case MEAT_RING: break; + case RIN_REGENERATION: + You_feel("enervated."); + learnring(obj, TRUE); + break; case RIN_STEALTH: toggle_stealth(obj, (EStealth & ~mask), FALSE); break; From f92b79fe8f18b3596fe25191f2a099091c7920ee Mon Sep 17 00:00:00 2001 From: copperwater Date: Wed, 13 Jun 2018 10:40:10 -0400 Subject: [PATCH 24/36] Wizard mode can override an artifact ignoring you --- src/artifact.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/artifact.c b/src/artifact.c index c71c0e87c1..864f5bd423 100644 --- a/src/artifact.c +++ b/src/artifact.c @@ -1451,9 +1451,11 @@ struct obj *obj; /* the artifact is tired :-) */ You_feel("that %s %s ignoring you.", the(xname(obj)), otense(obj, "are")); - /* and just got more so; patience is essential... */ - obj->age += (long) d(3, 10); - return 1; + if (!(wizard && yn("Override?") == 'y')) { + /* and just got more so; patience is essential... */ + obj->age += (long) d(3, 10); + return 1; + } } obj->age = g.monstermoves + rnz(100); @@ -1627,9 +1629,11 @@ struct obj *obj; u.uprops[oart->inv_prop].extrinsic ^= W_ARTI; You_feel("that %s %s ignoring you.", the(xname(obj)), otense(obj, "are")); - /* can't just keep repeatedly trying */ - obj->age += (long) d(3, 10); - return 1; + if (!(wizard && yn("Override?") == 'y')) { + /* can't just keep repeatedly trying */ + obj->age += (long) d(3, 10); + return 1; + } } else if (!on) { /* when turning off property, determine downtime */ /* arbitrary for now until we can tune this -dlc */ From 826f6ba07f51f976d5114db3e9076d3b6a2c2b3c Mon Sep 17 00:00:00 2001 From: copperwater Date: Mon, 20 Aug 2018 14:38:11 -0400 Subject: [PATCH 25/36] Monsters occupying the player's arrival spot are always moved Previously this happened half of the time and the other half the player was moved instead. In the interest of being fair to players who encounter e.g. a on-the-upstairs difficult bones file, or swarm of soldier ants, this guarantees that they will be able to escape. --- src/do.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/do.c b/src/do.c index f798642e6d..ea282a88dd 100644 --- a/src/do.c +++ b/src/do.c @@ -1275,14 +1275,8 @@ struct monst *mtmp; /* There's a monster at your target destination; it might be one which accompanied you--see mon_arrive(dogmove.c)--or perhaps - it was already here. Randomly move you to an adjacent spot - or else the monster to any nearby location. Prior to 3.3.0 - the latter was done unconditionally. */ - if (!rn2(2) && enexto(&cc, u.ux, u.uy, g.youmonst.data) - && distu(cc.x, cc.y) <= 2) - u_on_newpos(cc.x, cc.y); /*[maybe give message here?]*/ - else - mnexto(mtmp); + it was already here. Displace the monster to any nearby location. */ + mnexto(mtmp); if ((mtmp = m_at(u.ux, u.uy)) != 0) { /* there was an unconditional impossible("mnexto failed") From 7a9262c772dc018450649618af276117ec58e769 Mon Sep 17 00:00:00 2001 From: copperwater Date: Thu, 8 Nov 2018 23:39:00 -0500 Subject: [PATCH 26/36] Better message than "doesn't like your taste" New message is "thinks you would give it indigestion", which is a better indicator that you have slow digestion. A little more flavorful (ha). --- src/mhitu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mhitu.c b/src/mhitu.c index 533c1d3f93..a156b28653 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -2117,7 +2117,8 @@ struct attack *mattk; if (flags.verbose && (is_animal(mtmp->data) || (dmgtype(mtmp->data, AD_DGST) && Slow_digestion))) - pline("Obviously %s doesn't like your taste.", mon_nam(mtmp)); + pline("Obviously %s thinks you would give it indigestion.", + mon_nam(mtmp)); expels(mtmp, mtmp->data, FALSE); } return 1; From c090486b6dfa572e1a089079a5e6c936612b68b4 Mon Sep 17 00:00:00 2001 From: copperwater Date: Fri, 23 Nov 2018 19:48:43 -0500 Subject: [PATCH 27/36] Make rabid and sewer rats hide under objects This will also make them hide under furniture, due to an earlier change that allows that. Giant rats are of course too large to hide under things. --- src/monst.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/monst.c b/src/monst.c index b9a776a032..6cb6afff71 100644 --- a/src/monst.c +++ b/src/monst.c @@ -749,8 +749,8 @@ NEARDATA struct permonst mons_init[] = { A(ATTK(AT_BITE, AD_PHYS, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), SIZ(20, 12, MS_SQEEK, MZ_TINY), 0, 0, - M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE, M2_HOSTILE, M3_INFRAVISIBLE, - 1, CLR_BROWN), + M1_ANIMAL | M1_NOHANDS | M1_CARNIVORE | M1_CONCEAL, M2_HOSTILE, + M3_INFRAVISIBLE, 1, CLR_BROWN), MON("giant rat", S_RODENT, LVL(1, 10, 7, 0, 0), (G_GENO | G_SGROUP | 2), A(ATTK(AT_BITE, AD_PHYS, 1, 3), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), @@ -761,8 +761,8 @@ NEARDATA struct permonst mons_init[] = { A(ATTK(AT_BITE, AD_DRCO, 2, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), SIZ(30, 5, MS_SQEEK, MZ_TINY), MR_POISON, 0, - M1_ANIMAL | M1_NOHANDS | M1_POIS | M1_CARNIVORE, M2_HOSTILE, - M3_INFRAVISIBLE, 4, CLR_BROWN), + M1_ANIMAL | M1_NOHANDS | M1_POIS | M1_CARNIVORE | M1_CONCEAL, + M2_HOSTILE, M3_INFRAVISIBLE, 4, CLR_BROWN), MON("wererat", S_RODENT, LVL(2, 12, 6, 10, -7), (G_NOGEN | G_NOCORPSE), A(ATTK(AT_BITE, AD_WERE, 1, 4), NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK, NO_ATTK), From 5e5117218669dac53708878b1185068218f4d089 Mon Sep 17 00:00:00 2001 From: copperwater Date: Sat, 26 Jan 2019 23:16:26 -0500 Subject: [PATCH 28/36] Hungerless casting ignores "too hungry to cast" penalty This came from SliceHack, and it includes a refactor of one of the leading contenders for "ugliest section of code in NetHack", the switch statement determining spell hunger reduction. The refactor pulls the hunger reduction logic into its own function, and provides a macro that is used to check, essentially, whether you have hungerless casting. --- src/spell.c | 79 +++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/src/spell.c b/src/spell.c index 312ca44752..b971e4bdaa 100644 --- a/src/spell.c +++ b/src/spell.c @@ -42,6 +42,7 @@ static char *FDECL(spellretention, (int, char *)); static int NDECL(throwspell); static void NDECL(cast_protection); static void FDECL(spell_backfire, (int)); +static int FDECL(spell_hunger, (int)); static const char *FDECL(spelltypemnemonic, (int)); static boolean FDECL(spell_aim_step, (genericptr_t, int, int)); @@ -894,12 +895,47 @@ int spell; return; } +/* Given an expected amount of hunger for a spell, return the amount it should + * be reduced to. High-intelligence Wizards get to cast spells with less or no + * hunger penalty. */ +static int +spell_hunger(hungr) +int hungr; +{ + /* If hero is a wizard, their current intelligence + * (bonuses + temporary + current) + * affects hunger reduction in casting a spell. + * 1. int = 17-18 no reduction + * 2. int = 16 1/4 hungr + * 3. int = 15 1/2 hungr + * 4. int = 1-14 normal reduction + * The reason for this is: + * a) Intelligence affects the amount of exertion + * in thinking. + * b) Wizards have spent their life at magic and + * understand quite well how to cast spells. + */ + if (Role_if(PM_WIZARD)) { + int intel = acurr(A_INT); + if (intel >= 17) + return 0; + else if (intel == 16) + return hungr / 4; + else if (intel == 15) + return hungr / 2; + } + /* no adjustment */ + return hungr; +} +/* for using this function to test whether hunger would be eliminated */ +#define spell_would_hunger() (spell_hunger(100) > 0) + int spelleffects(spell, atme) int spell; boolean atme; { - int energy, damage, chance, n, intell; + int energy, damage, chance, n; int otyp, skill, role_skill, res = 0; boolean confused = (Confusion != 0); boolean physical_damage = FALSE; @@ -943,7 +979,8 @@ boolean atme; */ energy = (spellev(spell) * 5); /* 5 <= energy <= 35 */ - if (u.uhunger <= 10 && spellid(spell) != SPE_DETECT_FOOD) { + if (u.uhunger <= 10 && spellid(spell) != SPE_DETECT_FOOD + && spell_would_hunger() > 0) { You("are too hungry to cast that spell."); return 0; } else if (ACURR(A_STR) < 4 && spellid(spell) != SPE_RESTORE_ABILITY) { @@ -979,43 +1016,7 @@ boolean atme; return res; } else { if (spellid(spell) != SPE_DETECT_FOOD) { - int hungr = energy * 2; - - /* If hero is a wizard, their current intelligence - * (bonuses + temporary + current) - * affects hunger reduction in casting a spell. - * 1. int = 17-18 no reduction - * 2. int = 16 1/4 hungr - * 3. int = 15 1/2 hungr - * 4. int = 1-14 normal reduction - * The reason for this is: - * a) Intelligence affects the amount of exertion - * in thinking. - * b) Wizards have spent their life at magic and - * understand quite well how to cast spells. - */ - intell = acurr(A_INT); - if (!Role_if(PM_WIZARD)) - intell = 10; - switch (intell) { - case 25: - case 24: - case 23: - case 22: - case 21: - case 20: - case 19: - case 18: - case 17: - hungr = 0; - break; - case 16: - hungr /= 4; - break; - case 15: - hungr /= 2; - break; - } + int hungr = spell_hunger(energy * 2); /* don't put player (quite) into fainting from * casting a spell, particularly since they might * not even be hungry at the beginning; however, From de094a67fbf1c2f03d3161d703303962810962da Mon Sep 17 00:00:00 2001 From: copperwater Date: Thu, 21 Feb 2019 17:10:59 -0500 Subject: [PATCH 29/36] Refactor: mpickstuff takes callback instead of object class list This came about because I wanted to let likes_magic monsters pick up *any* item with oc_magic, not just amulets, rings, scrolls, potions, and spellbooks. But since e.g. TOOL_CLASS contains both magical and nonmagical items, it wouldn't be correct to add that to the list. This commit does away with the practice of passing a string of object class characters to mpickstuff, replacing it with a callback that simply takes an obj* and can do whatever it wants with that. All of the old behavior is kept, except for the planned gameplay change that the magical one now tests based off oc_magic rather than oclass. Interestingly, this means that some magical items can be informally identified by noticing a monster pick them up that wouldn't normally. This is a good little detail in my opinion. --- include/extern.h | 2 +- src/mon.c | 12 +++++--- src/monmove.c | 73 +++++++++++++++++++++++++++++++++++++----------- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/include/extern.h b/include/extern.h index fc3f78199c..918439daea 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1465,7 +1465,7 @@ E int FDECL(meatmetal, (struct monst *)); E int FDECL(meatobj, (struct monst *)); E int FDECL(meatcorpse, (struct monst *)); E void FDECL(mpickgold, (struct monst *)); -E boolean FDECL(mpickstuff, (struct monst *, const char *)); +E boolean FDECL(mpickstuff, (struct monst *, boolean (*)(OBJ_P))); E int FDECL(curr_mon_load, (struct monst *)); E int FDECL(max_mon_load, (struct monst *)); E int FDECL(can_carry, (struct monst *, struct obj *)); diff --git a/src/mon.c b/src/mon.c index 5342f20a5e..ca53c59907 100644 --- a/src/mon.c +++ b/src/mon.c @@ -1281,10 +1281,14 @@ register struct monst *mtmp; } } +/* Monster tries to pick up an item. Return TRUE if something was picked up. + * mitem_wanted is a callback that takes an object and returns TRUE if the item + * should be picked up or FALSE if not. + */ boolean -mpickstuff(mtmp, str) +mpickstuff(mtmp, mitem_wanted) register struct monst *mtmp; -register const char *str; +boolean FDECL ((*mitem_wanted), (OBJ_P)); { register struct obj *otmp, *otmp2, *otmp3; int carryamt = 0; @@ -1296,8 +1300,8 @@ register const char *str; for (otmp = g.level.objects[mtmp->mx][mtmp->my]; otmp; otmp = otmp2) { otmp2 = otmp->nexthere; /* Nymphs take everything. Most monsters don't pick up corpses. */ - if (!str ? searches_for_item(mtmp, otmp) - : !!(index(str, otmp->oclass))) { + if ((mitem_wanted != NULL && (*mitem_wanted)(otmp)) + || (mitem_wanted == NULL && searches_for_item(mtmp, otmp))) { if (otmp->otyp == CORPSE && mtmp->data->mlet != S_NYMPH /* let a handful of corpse types thru to can_carry() */ && !touch_petrifies(&mons[otmp->corpsenm]) diff --git a/src/monmove.c b/src/monmove.c index 736ec2f790..9930c0d4b5 100644 --- a/src/monmove.c +++ b/src/monmove.c @@ -751,14 +751,55 @@ register struct monst *mtmp; return (tmp == 2); } -static NEARDATA const char practical[] = { WEAPON_CLASS, ARMOR_CLASS, - GEM_CLASS, FOOD_CLASS, 0 }; -static NEARDATA const char magical[] = { AMULET_CLASS, POTION_CLASS, - SCROLL_CLASS, WAND_CLASS, - RING_CLASS, SPBOOK_CLASS, 0 }; -static NEARDATA const char indigestion[] = { BALL_CLASS, ROCK_CLASS, 0 }; -static NEARDATA const char boulder_class[] = { ROCK_CLASS, 0 }; -static NEARDATA const char gem_class[] = { GEM_CLASS, 0 }; +/* Return true if a "practical" monster should be interested in this obj. */ +boolean +mitem_practical(obj) +struct obj * obj; +{ + switch(obj->oclass) { + case WEAPON_CLASS: + case ARMOR_CLASS: + case GEM_CLASS: + case FOOD_CLASS: + return TRUE; + default: + return FALSE; + } +} + +/* Return true if a monster that likes magical items should be interested in + * this obj. */ +boolean +mitem_magical(obj) +struct obj * obj; +{ + return objects[obj->otyp].oc_magic; +} + +/* Return true if a monster (gelatinous cube) shouldn't try to eat this obj. */ +boolean +mitem_indigestion(obj) +struct obj * obj; +{ + return (obj->oclass == BALL_CLASS || obj->oclass == ROCK_CLASS); +} + +/* Return true if a giant should be interested in this obj. (They like all + * rocks.)*/ +boolean +mitem_rock(obj) +struct obj * obj; +{ + return (obj->oclass == ROCK_CLASS); +} + +/* Return true if a gem-liking monster should be interested in this obj. */ +boolean +mitem_gem(obj) +struct obj * obj; +{ + return (obj->oclass == GEM_CLASS); +} boolean itsstuck(mtmp) @@ -1110,18 +1151,18 @@ register int after; continue; if (((likegold && otmp->oclass == COIN_CLASS) - || (likeobjs && index(practical, otmp->oclass) + || (likeobjs && mitem_practical(otmp) && (otmp->otyp != CORPSE || (ptr->mlet == S_NYMPH && !is_rider(&mons[otmp->corpsenm])))) - || (likemagic && index(magical, otmp->oclass)) + || (likemagic && mitem_magical(otmp)) || (uses_items && searches_for_item(mtmp, otmp)) || (likerock && otmp->otyp == BOULDER) || (likegems && otmp->oclass == GEM_CLASS && objects[otmp->otyp].oc_material != MINERAL) || (conceals && !cansee(otmp->ox, otmp->oy)) || (ptr == &mons[PM_GELATINOUS_CUBE] - && !index(indigestion, otmp->oclass) + && !mitem_indigestion(otmp) && !(otmp->otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm])))) && touch_artifact(otmp, mtmp)) { @@ -1575,15 +1616,15 @@ register int after; boolean picked = FALSE; if (likeobjs) - picked |= mpickstuff(mtmp, practical); + picked |= mpickstuff(mtmp, mitem_practical); if (likemagic) - picked |= mpickstuff(mtmp, magical); + picked |= mpickstuff(mtmp, mitem_magical); if (likerock) - picked |= mpickstuff(mtmp, boulder_class); + picked |= mpickstuff(mtmp, mitem_rock); if (likegems) - picked |= mpickstuff(mtmp, gem_class); + picked |= mpickstuff(mtmp, mitem_gem); if (uses_items) - picked |= mpickstuff(mtmp, (char *) 0); + picked |= mpickstuff(mtmp, NULL); if (picked) mmoved = 3; } From 275bfb8d8176e413ff7d909e3dba8caac0646c8d Mon Sep 17 00:00:00 2001 From: copperwater Date: Wed, 4 Sep 2019 12:12:48 -0400 Subject: [PATCH 30/36] Remove limit of 5 unrecognized wish attempts This was an annoying quirk of the wishing code. If it couldn't parse your input 5 times, it would give you a random object you almost certainly don't want. What exactly is the point of this? Fix this by allowing the user to enter malformed or unrecognizable input an indefinite amount of times. They'll get it right eventually, or at least get prompted to enter "help" for assistance and see their option of getting out by entering "nothing", if they don't want anything. --- src/zap.c | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/src/zap.c b/src/zap.c index 4d03a6a96b..571b915b69 100644 --- a/src/zap.c +++ b/src/zap.c @@ -26,7 +26,7 @@ static void FDECL(backfire, (struct obj *)); static void FDECL(boxlock_invent, (struct obj *)); static int FDECL(spell_hit_bonus, (int)); static void FDECL(destroy_one_item, (struct obj *, int, int)); -static void FDECL(wishcmdassist, (int)); +static void NDECL(wishcmdassist); #define ZT_MAGIC_MISSILE (AD_MAGM - 1) #define ZT_FIRE (AD_FIRE - 1) @@ -5343,11 +5343,8 @@ int damage, tell; return resisted; } -#define MAXWISHTRY 5 - static void -wishcmdassist(triesleft) -int triesleft; +wishcmdassist() { static NEARDATA const char * wishinfo[] = { @@ -5369,16 +5366,12 @@ int triesleft; 0, }, preserve_wishless[] = "Doing so will preserve 'wishless' conduct.", - retry_info[] = - "If you specify an unrecognized object name %s%s time%s,", - retry_too[] = "a randomly chosen item will be granted.", suppress_cmdassist[] = "(Suppress this assistance with !cmdassist in your config file.)", *cardinals[] = { "zero", "one", "two", "three", "four", "five" }, too_many[] = "too many"; int i; winid win; - char buf[BUFSZ]; win = create_nhwindow(NHW_TEXT); if (!win) @@ -5388,14 +5381,6 @@ int triesleft; if (!u.uconduct.wishes) putstr(win, 0, preserve_wishless); putstr(win, 0, ""); - Sprintf(buf, retry_info, - (triesleft >= 0 && triesleft < SIZE(cardinals)) - ? cardinals[triesleft] - : too_many, - (triesleft < MAXWISHTRY) ? " more" : "", - plur(triesleft)); - putstr(win, 0, buf); - putstr(win, 0, retry_too); putstr(win, 0, ""); if (iflags.cmdassist) putstr(win, 0, suppress_cmdassist); @@ -5425,7 +5410,7 @@ makewish() if (buf[0] == '\033') { buf[0] = '\0'; } else if (!strcmpi(buf, "help")) { - wishcmdassist(MAXWISHTRY - tries); + wishcmdassist(); buf[0] = '\0'; /* for EDIT_GETLIN */ goto retry; } @@ -5444,12 +5429,8 @@ makewish() otmp = readobjnam(buf, ¬hing); if (!otmp) { pline("Nothing fitting that description exists in the game."); - if (++tries < MAXWISHTRY) - goto retry; - pline1(thats_enough_tries); - otmp = readobjnam((char *) 0, (struct obj *) 0); - if (!otmp) - return; /* for safety; should never happen */ + tries++; + goto retry; } else if (otmp == ¬hing) { /* explicitly wished for "nothing", presumably attempting to retain wishless conduct */ From dcba0397c8e7b415f7832c82640f741932c914de Mon Sep 17 00:00:00 2001 From: copperwater Date: Wed, 4 Sep 2019 16:31:27 -0400 Subject: [PATCH 31/36] Hallucination always blocks floating eye gaze Previously this happened, but only 75% of the time. Make this a certainty instead; this makes it a valid strategy to make yourself hallucinate when fighting floating eyes. --- src/uhitm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uhitm.c b/src/uhitm.c index 0c901d0676..2d8240b90b 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -2971,7 +2971,7 @@ boolean wep_was_destroyed; if (ureflects("%s gaze is reflected by your %s.", s_suffix(Monnam(mon)))) { ; - } else if (Hallucination && rn2(4)) { + } else if (Hallucination) { /* [it's the hero who should be getting paralyzed and isn't; this message describes the monster's reaction rather than the hero's escape] */ From 7c1cb592a204a6728ec88a1455471cb922984879 Mon Sep 17 00:00:00 2001 From: copperwater Date: Mon, 9 Sep 2019 20:55:11 -0400 Subject: [PATCH 32/36] Hallucinatory rays Inspired by a YANI by Kahran042 for hallucinatory breath weapon names. I started out just implementing that, but ended up expanding it (since it doesn't look very good to have "The flesh golem breathes glitter! The blast of fire misses you. The blast of fire bounces! The blast of fire hits you! The blast of fire misses the tribble.") So now, every ray - wands, spells, and breath weapons alike - will be described as a blast of something ridiculous when you're hallucinating. (This is suppressed for the occasions where it's writing a possible death reason; the death will report the real type of ray that killed you). This removes most toying with a const char* variable fltxt, replacing it with calls to a new function flash_str. I first tried just editing fltxt, but then you get the same blast for every bounce, miss, and hit of a given ray, and since the original breath uses a different codepath, they wouldn't be the same. So this rerandomizes the blast for each message that gets printed for the ray. --- include/extern.h | 1 + src/mcastu.c | 7 +++--- src/mthrowu.c | 28 ++++++++++++++++++++- src/zap.c | 63 ++++++++++++++++++++++++++++++++++-------------- 4 files changed, 76 insertions(+), 23 deletions(-) diff --git a/include/extern.h b/include/extern.h index 918439daea..035a10fb52 100644 --- a/include/extern.h +++ b/include/extern.h @@ -3178,6 +3178,7 @@ E void FDECL(destroy_item, (int, int)); E int FDECL(destroy_mitem, (struct monst *, int, int)); E int FDECL(resist, (struct monst *, CHAR_P, int, int)); E void NDECL(makewish); +E const char* FDECL(flash_str, (int, BOOLEAN_P)); #endif /* !MAKEDEFS_C && !MDLIB_C */ diff --git a/src/mcastu.c b/src/mcastu.c index f6b23986e3..b2cd11834e 100644 --- a/src/mcastu.c +++ b/src/mcastu.c @@ -45,8 +45,6 @@ static boolean FDECL(is_undirected_spell, (unsigned int, int)); static boolean FDECL(spell_would_be_useless, (struct monst *, unsigned int, int)); -extern const char *const flash_types[]; /* from zap.c */ - /* feedback when frustrated monster couldn't cast a spell */ static void @@ -859,9 +857,10 @@ register struct attack *mattk; if (lined_up(mtmp) && rn2(3)) { nomul(0); if (mattk->adtyp && (mattk->adtyp < 11)) { /* no cf unsigned >0 */ - if (canseemon(mtmp)) + if (canseemon(mtmp)) { pline("%s zaps you with a %s!", Monnam(mtmp), - flash_types[ad_to_typ(mattk->adtyp)]); + flash_str(ad_to_typ(mattk->adtyp), FALSE)); + } buzz(-ad_to_typ(mattk->adtyp), (int) mattk->damn, mtmp->mx, mtmp->my, sgn(g.tbx), sgn(g.tby)); } else diff --git a/src/mthrowu.c b/src/mthrowu.c index 4bae7d7885..bba58dc706 100644 --- a/src/mthrowu.c +++ b/src/mthrowu.c @@ -26,6 +26,19 @@ static NEARDATA const char *breathwep[] = { "strange breath #9" }; +/* also used extern in zap.c + * need exact number because both files need to do rn2(SIZE(hallublasts)) */ +const char* const hallublasts[49] = { + "bubbles", "butterflies", "champagne", "chaos", "coins", "cotton candy", + "crumbs", "dark matter", "darkness", "emotions", "flowers", "fog", + "gelatin", "gemstones", "ghosts", "glass shards", "glitter", "gravel", + "gravity", "gravy", "holy light", "hornets", "hyphens", "laser beams", + "magma", "mathematics", "meteors", "music", "needles", "noise", "nostalgia", + "oil", "plasma", "prismatic light", "purple", "rope", "salt", "sand", + "scrolls", "sludge", "snowflakes", "sparkles", "spores", "steam", + "tetrahedrons", "text", "toxic waste", "water", "wind" +}; + boolean m_has_launcher_and_ammo(mtmp) struct monst *mtmp; @@ -790,6 +803,19 @@ struct attack *mattk; return 0; } +/* Return the name of a breath weapon. If the player is hallucinating, return + * a silly name instead. + * typ is AD_MAGM, AD_FIRE, etc */ +static const char* +breathwep_name(typ) +int typ; +{ + if (Hallucination) + return hallublasts[rn2(SIZE(hallublasts))]; + + return breathwep[typ - 1]; +} + /* monster breathes at monster (ranged) */ int breamm(mtmp, mattk, mtarg) @@ -813,7 +839,7 @@ struct attack *mattk; if ((typ >= AD_MAGM) && (typ <= AD_ACID)) { boolean utarget = (mtarg == &g.youmonst); if (canseemon(mtmp)) - pline("%s breathes %s!", Monnam(mtmp), breathwep[typ - 1]); + pline("%s breathes %s!", Monnam(mtmp), breathwep_name(typ)); dobuzz((int) (-20 - (typ - 1)), (int) mattk->damn, mtmp->mx, mtmp->my, sgn(g.tbx), sgn(g.tby), utarget); nomul(0); diff --git a/src/zap.c b/src/zap.c index 571b915b69..43b0b2b043 100644 --- a/src/zap.c +++ b/src/zap.c @@ -67,6 +67,8 @@ const char *const flash_types[] = /* also used in buzzmu(mcastu.c) */ "blast of poison gas", "blast of acid", "", "" }; +extern const char* const hallublasts[49]; /* hallucinatory blasts [mthrowu.c] */ + /* * Recognizing unseen wands by zapping: in 3.4.3 and earlier, zapping * most wand types while blind would add that type to the discoveries @@ -2682,9 +2684,10 @@ ubreatheu(mattk) struct attack *mattk; { int dtyp = 20 + mattk->adtyp - 1; /* breath by hero */ - const char *fltxt = flash_types[dtyp]; /* blast of */ - zhitu(dtyp, mattk->damn, fltxt, u.ux, u.uy); + /* zhitu doesn't print the flash string; it only needs it for losehp and + * killer. Suppress hallucinatory ray names. */ + zhitu(dtyp, mattk->damn, flash_str(dtyp, TRUE), u.ux, u.uy); } /* light damages hero in gremlin form */ @@ -4160,14 +4163,13 @@ boolean say; /* Announce out of sight hit/miss events if true */ struct monst *mon; coord save_bhitpos; boolean shopdamage = FALSE; - const char *fltxt; struct obj *otmp; int spell_type; + int typ = (type <= -30) ? abstype : abs(type); /* if its a Hero Spell then get its SPE_TYPE */ spell_type = is_hero_spell(type) ? SPE_MAGIC_MISSILE + abstype : 0; - fltxt = flash_types[(type <= -30) ? abstype : abs(type)]; if (u.uswallow) { register int tmp; @@ -4177,8 +4179,8 @@ boolean say; /* Announce out of sight hit/miss events if true */ if (!u.ustuck) u.uswallow = 0; else - pline("%s rips into %s%s", The(fltxt), mon_nam(u.ustuck), - exclam(tmp)); + pline("%s rips into %s%s", The(flash_str(typ, FALSE)), + mon_nam(u.ustuck), exclam(tmp)); /* Using disintegration from the inside only makes a hole... */ if (tmp == MAGIC_COOKIE) u.ustuck->mhp = 0; @@ -4235,7 +4237,7 @@ boolean say; /* Announce out of sight hit/miss events if true */ if (zap_hit(find_mac(mon), spell_type)) { if (mon_reflects(mon, (char *) 0)) { if (cansee(mon->mx, mon->my)) { - hit(fltxt, mon, exclam(0)); + hit(flash_str(typ, FALSE), mon, exclam(0)); shieldeff(mon->mx, mon->my); (void) mon_reflects(mon, "But it reflects from %s %s!"); @@ -4249,7 +4251,7 @@ boolean say; /* Announce out of sight hit/miss events if true */ if (is_rider(mon->data) && abs(type) == ZT_BREATH(ZT_DEATH)) { if (canseemon(mon)) { - hit(fltxt, mon, "."); + hit(flash_str(typ, FALSE), mon, "."); pline("%s disintegrates.", Monnam(mon)); pline("%s body reintegrates before your %s!", s_suffix(Monnam(mon)), @@ -4263,7 +4265,7 @@ boolean say; /* Announce out of sight hit/miss events if true */ } if (mon->data == &mons[PM_DEATH] && abstype == ZT_DEATH) { if (canseemon(mon)) { - hit(fltxt, mon, "."); + hit(flash_str(typ, FALSE), mon, "."); pline("%s absorbs the deadly %s!", Monnam(mon), type == ZT_BREATH(ZT_DEATH) ? "blast" : "ray"); @@ -4273,11 +4275,11 @@ boolean say; /* Announce out of sight hit/miss events if true */ } if (tmp == MAGIC_COOKIE) { /* disintegration */ - disintegrate_mon(mon, type, fltxt); + disintegrate_mon(mon, type, flash_str(typ, FALSE)); } else if (DEADMONSTER(mon)) { if (type < 0) { /* mon has just been killed by another monster */ - monkilled(mon, fltxt, AD_RBRE); + monkilled(mon, flash_str(typ, FALSE), AD_RBRE); } else { int xkflags = XKILL_GIVEMSG; /* killed(mon); */ @@ -4295,7 +4297,7 @@ boolean say; /* Announce out of sight hit/miss events if true */ if (!otmp) { /* normal non-fatal hit */ if (say || canseemon(mon)) - hit(fltxt, mon, exclam(tmp)); + hit(flash_str(typ, FALSE), mon, exclam(tmp)); } else { /* some armor was destroyed; no damage done */ if (canseemon(mon)) @@ -4311,7 +4313,7 @@ boolean say; /* Announce out of sight hit/miss events if true */ range -= 2; } else { if (say || canseemon(mon)) - miss(fltxt, mon); + miss(flash_str(typ, FALSE), mon); } } else if (sx == u.ux && sy == u.uy && range >= 0) { nomul(0); @@ -4320,7 +4322,7 @@ boolean say; /* Announce out of sight hit/miss events if true */ goto buzzmonst; } else if (zap_hit((int) u.uac, 0)) { range -= 2; - pline("%s hits you!", The(fltxt)); + pline("%s hits you!", The(flash_str(typ, FALSE))); if (Reflecting) { if (!Blind) { (void) ureflects("But %s reflects from your %s!", @@ -4331,10 +4333,12 @@ boolean say; /* Announce out of sight hit/miss events if true */ dy = -dy; shieldeff(sx, sy); } else { - zhitu(type, nd, fltxt, sx, sy); + /* flash_str here only used for killer; suppress + * hallucination */ + zhitu(type, nd, flash_str(typ, TRUE), sx, sy); } } else if (!Blind) { - pline("%s whizzes by you!", The(fltxt)); + pline("%s whizzes by you!", The(flash_str(typ, FALSE))); } else if (abstype == ZT_LIGHTNING) { Your("%s tingles.", body_part(ARM)); } @@ -4359,7 +4363,8 @@ boolean say; /* Announce out of sight hit/miss events if true */ if ((--range > 0 && isok(lsx, lsy) && cansee(lsx, lsy)) || fireball) { if (Is_airlevel(&u.uz)) { /* nothing to bounce off of */ - pline_The("%s vanishes into the aether!", fltxt); + pline_The("%s vanishes into the aether!", + flash_str(typ, FALSE)); if (fireball) type = ZT_WAND(ZT_FIRE); /* skip pending fireball */ break; @@ -4368,7 +4373,7 @@ boolean say; /* Announce out of sight hit/miss events if true */ sy = lsy; break; /* fireballs explode before the obstacle */ } else - pline_The("%s bounces!", fltxt); + pline_The("%s bounces!", flash_str(typ, FALSE)); } if (!dx || !dy || !rn2(bchance)) { dx = -dx; @@ -5459,4 +5464,26 @@ makewish() } } +/* Fills buf with the appropriate string for this ray. + * In the hallucination case, insert "blast of ". + * Assumes that the caller will specify typ in the appropriate range for + * wand/spell/breath weapon. */ +const char* +flash_str(typ, nohallu) +int typ; +boolean nohallu; /* suppress hallucination (for death reasons) */ +{ + static char fltxt[BUFSZ]; + if (Hallucination && !nohallu) { + /* always return "blast of foo" for simplicity. + * This could be extended with hallucinatory rays, but probably not worth + * it at this time. */ + Sprintf(fltxt, "blast of %s", hallublasts[rn2(SIZE(hallublasts))]); + } + else { + Strcpy(fltxt, flash_types[typ]); + } + return fltxt; +} + /*zap.c*/ From d4ec476c5ba9d071ae8b8f25b49bf65417be2fa7 Mon Sep 17 00:00:00 2001 From: copperwater Date: Tue, 17 Sep 2019 19:36:11 -0400 Subject: [PATCH 33/36] "Mortals cannot enter the Mazes of Menace more than once" An attempt to smooth the rather jarring transition between messages like "You float gently down to earth. Do you want your possessions identified?" The new message doesn't say why you can't reenter the Mazes, only that you can't. It's probably classified Yendorian information. --- src/teleport.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/teleport.c b/src/teleport.c index f51001a7e8..63310ebc0f 100644 --- a/src/teleport.c +++ b/src/teleport.c @@ -996,6 +996,10 @@ level_tele() /* calls done(ESCAPED) if newlevel==0 */ if (escape_by_flying) { You("%s.", escape_by_flying); + /* Why can't you just walk back into the dungeon? + * This doesn't give an answer to that, but at least it states that + * there's some reason that you can't. */ + pline("But mortals cannot enter the Mazes of Menace more than once..."); /* [dlevel used to be set to 1, but it doesn't make sense to teleport out of the dungeon and float or fly down to the surface but then actually arrive back inside the dungeon] */ From 942d8e5dbe33160d57d9b8161fc27b6ede809025 Mon Sep 17 00:00:00 2001 From: copperwater Date: Thu, 19 Sep 2019 13:47:23 -0400 Subject: [PATCH 34/36] Scale monster group size based on dungeon level, not character level There are an lot of things in NetHack that scale on the player's experience level. This was one of them. But the group size should depend on the local difficulty of the area, not the player - this makes flavor sense (why would a level 1 pacifist on dungeon level 23 encounter smaller hordes of orcs than an appropriately leveled player would?) and also practical sense (reducing your own level shouldn't be rewarded with an easier dungeon.) This also removes a compiler bug hack in m_initgrp that was last reported at least seventeen years and 6 major GCC versions ago. I doubt it's causing problems any longer. --- src/makemon.c | 42 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/src/makemon.c b/src/makemon.c index 3af9524ba2..0bf6c9b0c3 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -79,41 +79,17 @@ int x, y, n, mmflags; { coord mm; register int cnt = rnd(n); + int diff = level_difficulty(); struct monst *mon; -#if defined(__GNUC__) && (defined(HPUX) || defined(DGUX)) - /* There is an unresolved problem with several people finding that - * the game hangs eating CPU; if interrupted and restored, the level - * will be filled with monsters. Of those reports giving system type, - * there were two DG/UX and two HP-UX, all using gcc as the compiler. - * hcroft@hpopb1.cern.ch, using gcc 2.6.3 on HP-UX, says that the - * problem went away for him and another reporter-to-newsgroup - * after adding this debugging code. This has almost got to be a - * compiler bug, but until somebody tracks it down and gets it fixed, - * might as well go with the "but it went away when I tried to find - * it" code. - */ - int cnttmp, cntdiv; - - cnttmp = cnt; - debugpline4("init group call <%d,%d>, n=%d, cnt=%d.", x, y, n, cnt); - cntdiv = ((u.ulevel < 3) ? 4 : (u.ulevel < 5) ? 2 : 1); -#endif - /* Tuning: cut down on swarming at low character levels [mrs] */ - cnt /= (u.ulevel < 3) ? 4 : (u.ulevel < 5) ? 2 : 1; -#if defined(__GNUC__) && (defined(HPUX) || defined(DGUX)) - if (cnt != (cnttmp / cntdiv)) { - pline("cnt=%d using %d, cnttmp=%d, cntdiv=%d", cnt, - (u.ulevel < 3) ? 4 : (u.ulevel < 5) ? 2 : 1, cnttmp, cntdiv); - } -#endif - if (!cnt) + /* Tuning: cut down on swarming at low dungeon levels */ + if (diff < 3) + cnt /= 4; + else if (diff < 5) + cnt /= 2; + else if (diff < 7) + cnt = (cnt * 3) / 4; + if (cnt <= 0) cnt++; -#if defined(__GNUC__) && (defined(HPUX) || defined(DGUX)) - if (cnt < 0) - cnt = 1; - if (cnt > 10) - cnt = 10; -#endif mm.x = x; mm.y = y; From 9db35170e17b407e6523845e4d81b4af39fa8234 Mon Sep 17 00:00:00 2001 From: copperwater Date: Sun, 29 Sep 2019 23:49:31 -0400 Subject: [PATCH 35/36] Prevent priest-type monsters from receiving a cursed mace Why did the code go out of its way to make sure these maces had a 50% chance to be cursed? They're priests. They should be able to deal with curses on their own equipment. Does not affect the special case of the mace being made +1, +2, or +3. --- src/makemon.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/makemon.c b/src/makemon.c index 0bf6c9b0c3..7bd66a285d 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -233,8 +233,7 @@ register struct monst *mtmp; || quest_mon_represents_role(ptr, PM_PRIEST)) { otmp = mksobj(MACE, FALSE, FALSE); otmp->spe = rnd(3); - if (!rn2(2)) - curse(otmp); + otmp->cursed = 0; (void) mpickobj(mtmp, otmp); } else if (mm == PM_NINJA) { /* extra quest villains */ (void) mongets(mtmp, rn2(4) ? SHURIKEN : DART); From ef4bbdc9cb17921ff5a3ffc6e7f96432dec59978 Mon Sep 17 00:00:00 2001 From: copperwater Date: Tue, 1 Oct 2019 16:14:09 -0400 Subject: [PATCH 36/36] Place Fort Ludios portal in the first eligible vault generated From UnNetHack. No modification is made to the prerequisites for the portal - a vault in the main dungeon below level 10, not on a level where another branch splits off - but it will now not randomly decide to skip the vault when generating a level and maybe defer to some later vault. There are two reasons for this. First, to make Ludios accessible in practically every game. It will only be inaccessible if no vault generates on every single level between level 11 and the Castle, which is a very small chance. Second, because this biases the portal to be placed rather high up in the dungeon, often before the hero's really ready to take on Ludios, and it's more interesting to have a branch that is discovered then deferred and returned to later than a branch that is discovered then immediately cleared without much effort. --- src/mklev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mklev.c b/src/mklev.c index 79a4f87595..b43067785c 100644 --- a/src/mklev.c +++ b/src/mklev.c @@ -2043,8 +2043,8 @@ xchar x, y; source = &br->end1; } - /* Already set or 2/3 chance of deferring until a later level. */ - if (source->dnum < g.n_dgns || (rn2(3) && !wizard)) + /* Already set. */ + if (source->dnum < g.n_dgns) return; if (!(u.uz.dnum == oracle_level.dnum /* in main dungeon */