diff --git a/fountain.c b/fountain.c new file mode 100644 index 0000000..9ceb75d --- /dev/null +++ b/fountain.c @@ -0,0 +1,710 @@ +/* NetHack 3.7 fountain.c $NHDT-Date: 1687058871 2023/06/18 03:27:51 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.95 $ */ +/* Copyright Scott R. Turner, srt@ucla, 10/27/86 */ +/* NetHack may be freely redistributed. See license for details. */ + +/* Code for drinking from fountains. */ + +#include "hack.h" + +static void dowatersnakes(void); +static void dowaterdemon(void); +static void dowaternymph(void); +static void gush(coordxy, coordxy, genericptr_t); +static void dofindgem(void); +static boolean watchman_warn_fountain(struct monst *); + +DISABLE_WARNING_FORMAT_NONLITERAL + +/* used when trying to dip in or drink from fountain or sink or pool while + levitating above it, or when trying to move downwards in that state */ +void +floating_above(const char *what) +{ + const char *umsg = "are floating high above the %s."; + + if (u.utrap && (u.utraptype == TT_INFLOOR || u.utraptype == TT_LAVA)) { + /* when stuck in floor (not possible at fountain or sink location, + so must be attempting to move down), override the usual message */ + umsg = "are trapped in the %s."; + what = surface(u.ux, u.uy); /* probably redundant */ + } + You(umsg, what); +} + +RESTORE_WARNING_FORMAT_NONLITERAL + +/* Fountain of snakes! */ +static void +dowatersnakes(void) +{ + register int num = rn1(5, 2); // Generate a random number between 2 and 5 to represent the number of water snakes + struct monst *mtmp; + + if (!(gm.mvitals[PM_WATER_MOCCASIN].mvflags & G_GONE)) { // Check if water snakes are extinct + if (!Blind) { + pline("An endless stream of %s pours forth!", + Hallucination ? makeplural(rndmonnam(NULL)) : "snakes"); + } else { + Soundeffect(se_snakes_hissing, 75); // Play a sound effect of hissing snakes + You_hear("%s hissing!", something); // Output the sound of hissing + } + while (num-- > 0) { + if ((mtmp = makemon(&mons[PM_WATER_MOCCASIN], u.ux, u.uy, MM_NOMSG)) != 0 + && t_at(mtmp->mx, mtmp->my)) { + (void) mintrap(mtmp, NO_TRAP_FLAGS); // Place the water snake on the map and check for traps + } + } + } else { + Soundeffect(se_furious_bubbling, 20); // Play a sound effect of furious bubbling + pline_The("fountain bubbles furiously for a moment, then calms."); // Output a message about the fountain bubbling + } +} + + +/* Water demon */ +static void +dowaterdemon(void) +{ + struct monst *mtmp; + + if (!(gm.mvitals[PM_WATER_DEMON].mvflags & G_GONE)) { + if ((mtmp = makemon(&mons[PM_WATER_DEMON], u.ux, u.uy, + MM_NOMSG)) != 0) { + if (!Blind) + You("unleash %s!", a_monnam(mtmp)); + else + You_feel("the presence of evil."); + + /* Give those on low levels a (slightly) better chance of survival + */ + if (rnd(100) > (80 + level_difficulty())) { + pline("Grateful for %s release, %s grants you a wish!", + mhis(mtmp), mhe(mtmp)); + /* give a wish and discard the monster (mtmp set to null) */ + mongrantswish(&mtmp); + } else if (t_at(mtmp->mx, mtmp->my)) + (void) mintrap(mtmp, NO_TRAP_FLAGS); + } + } else { + Soundeffect(se_furious_bubbling, 20); + pline_The("fountain bubbles furiously for a moment, then calms."); + } +} + +/* Water Nymph */ +static void +dowaternymph(void) +{ + register struct monst *mtmp; + + if (!(gm.mvitals[PM_WATER_NYMPH].mvflags & G_GONE) // Check if water nymphs are extinct + && (mtmp = makemon(&mons[PM_WATER_NYMPH], u.ux, u.uy, MM_NOMSG)) != 0) { // Place a water nymph on the map + if (!Blind) + You("attract %s!", a_monnam(mtmp)); // Output a message indicating the player attracted the water nymph + else + You_hear("a seductive voice."); // Output a message indicating the sound of a seductive voice + mtmp->msleeping = 0; + if (t_at(mtmp->mx, mtmp->my)) + (void) mintrap(mtmp, NO_TRAP_FLAGS); // Check for traps at the location of the water nymph + } else if (!Blind) { + Soundeffect(se_bubble_rising, 50); // Play a sound effect of a rising bubble + Soundeffect(se_loud_pop, 50); // Play a sound effect of a loud pop + pline("A large bubble rises to the surface and pops."); // Output a message about a large bubble popping + } else { + Soundeffect(se_loud_pop, 50); // Play a sound effect of a loud pop + You_hear("a loud pop."); // Output a message indicating the sound of a loud pop + } +} + + +/* Gushing forth along LOS from (u.ux, u.uy) */ +/* + * Function to handle fountain gushing event + */ +void dogushforth(int drinking) +{ + int madepool = 0; + + /* + * Clear the area around player's position and check for fountain gushing + */ + do_clear_area(u.ux, u.uy, 7, gush, (genericptr_t) &madepool); + + /* + * If no pool was generated, display appropriate message based on whether the player is drinking or not + */ + if (!madepool) { + if (drinking) + Your("thirst is quenched."); + else + pline("Water sprays all over you."); + } +} + +/* + * Callback function for do_clear_area to handle each grid square + */ +static void gush(coordxy x, coordxy y, genericptr_t poolcnt) +{ + register struct monst *mtmp; + register struct trap *ttmp; + + /* + * Check various conditions to determine if a pool can be created at the specified location + */ + if (((x + y) % 2) || u_at(x, y) + || (rn2(1 + distmin(u.ux, u.uy, x, y))) || (levl[x][y].typ != ROOM) + || (sobj_at(BOULDER, x, y)) || nexttodoor(x, y)) + return; + + /* + * If there's a trap at the location, attempt to remove it + */ + if ((ttmp = t_at(x, y)) != 0 && !delfloortrap(ttmp)) + return; + + /* + * If a pool is created for the first time, display a message + */ + if (!((*(int *) poolcnt)++)) + pline("Water gushes forth from the overflowing fountain!"); +} + + /* Put a pool at x, y */ + set_levltyp(x, y, POOL); + levl[x][y].flags = 0; + /* No kelp! */ + del_engr_at(x, y); + water_damage_chain(gl.level.objects[x][y], TRUE); + + if ((mtmp = m_at(x, y)) != 0) + (void) minliquid(mtmp); + else + newsym(x, y); +} + +/* Find a gem in the sparkling waters. */ +static void +dofindgem(void) +{ + if (!Blind) + You("spot a gem in the sparkling waters!"); // If the player is not blind, they spot a gem in the fountain. + else + You_feel("a gem here!"); // If the player is blind, they feel a gem in the fountain. + (void) mksobj_at(rnd_class(DILITHIUM_CRYSTAL, LUCKSTONE - 1), u.ux, u.uy, + FALSE, FALSE); // Create a random gem object at the player's location. + SET_FOUNTAIN_LOOTED(u.ux, u.uy); // Set the fountain as looted. + newsym(u.ux, u.uy); // Update the symbol of the fountain on the map. + exercise(A_WIS, TRUE); /* a discovery! */ // Increase the player's wisdom experience. +} + +static boolean +watchman_warn_fountain(struct monst *mtmp) +{ + if (is_watch(mtmp->data) && couldsee(mtmp->mx, mtmp->my) + && mtmp->mpeaceful) { + if (!Deaf) { + pline("%s yells:", Amonnam(mtmp)); + verbalize("Hey, stop using that fountain!"); // If a peaceful watchman can see the player, they yell at the player to stop using the fountain. + } else { + pline("%s earnestly %s %s %s!", + Amonnam(mtmp), + nolimbs(mtmp->data) ? "shakes" : "waves", + mhis(mtmp), + nolimbs(mtmp->data) + ? mbodypart(mtmp, HEAD) + : makeplural(mbodypart(mtmp, ARM))); // If the player is deaf, the watchman communicates their message through body language. + } + return TRUE; + } + return FALSE; +} + +void +dryup(coordxy x, coordxy y, boolean isyou) +{ + if (IS_FOUNTAIN(levl[x][y].typ) + && (!rn2(3) || FOUNTAIN_IS_WARNED(x, y))) { + if (isyou && in_town(x, y) && !FOUNTAIN_IS_WARNED(x, y)) { + struct monst *mtmp; + + SET_FOUNTAIN_WARNED(x, y); + /* Warn about future fountain use. */ + mtmp = get_iter_mons(watchman_warn_fountain); + /* You can see or hear this effect */ + if (!mtmp) + pline_The("flow reduces to a trickle."); // If the player is in a town and no warning has been issued, issue the warning for future fountain use. + return; + } + if (isyou && wizard) { + if (y_n("Dry up fountain?") == 'n') + return; + } + /* FIXME: sight-blocking clouds should use block_point() when + being created and unblock_point() when going away, then this + glyph hackery wouldn't be necessary */ + if (cansee(x, y)) { + int glyph = glyph_at(x, y); + + if (!glyph_is_cmap(glyph) || glyph_to_cmap(glyph) != S_cloud) + pline_The("fountain dries up!"); // If the player can see, display the message that the fountain dries up. + } + /* replace the fountain with ordinary floor */ + set_levltyp(x, y, ROOM); /* updates level.flags.nfountains */ // Change the tile of the fountain to ordinary floor. + levl[x][y].flags = 0; + levl[x][y].blessedftn = 0; + /* The location is seen if the hero/monster is invisible + or felt if the hero is blind. */ + newsym(x, y); // Update the symbol of the fountain on the map. + if (isyou && in_town(x, y)) + (void) angry_guards(FALSE); // If the fountain was located in a town, generate angry guards. + } +} + +void +drinkfountain(void) +{ + /* What happens when you drink from a fountain? */ + register boolean mgkftn = (levl[u.ux][u.uy].blessedftn == 1); + register int fate = rnd(30); + + if (Levitation) { + floating_above("fountain"); + return; + } + + if (mgkftn && u.uluck >= 0 && fate >= 10) { + int i, ii, littleluck = (u.uluck < 4); + + pline("Wow! This makes you feel great!"); + /* blessed restore ability */ + for (ii = 0; ii < A_MAX; ii++) + if (ABASE(ii) < AMAX(ii)) { + ABASE(ii) = AMAX(ii); + gc.context.botl = 1; + } + /* gain ability, blessed if "natural" luck is high */ + i = rn2(A_MAX); /* start at a random attribute */ + for (ii = 0; ii < A_MAX; ii++) { + if (adjattrib(i, 1, littleluck ? -1 : 0) && littleluck) + break; + if (++i >= A_MAX) + i = 0; + } + display_nhwindow(WIN_MESSAGE, FALSE); + pline("A wisp of vapor escapes the fountain..."); + exercise(A_WIS, TRUE); + levl[u.ux][u.uy].blessedftn = 0; + return; + } + + if (fate < 10) { + pline_The("cool draught refreshes you."); + u.uhunger += rnd(10); /* don't choke on water */ + newuhs(FALSE); + if (mgkftn) + return; + } else { + switch (fate) { + case 19: /* Self-knowledge */ + You_feel("self-knowledgeable..."); + display_nhwindow(WIN_MESSAGE, FALSE); + enlightenment(MAGICENLIGHTENMENT, ENL_GAMEINPROGRESS); + exercise(A_WIS, TRUE); + pline_The("feeling subsides."); + break; + case 20: /* Foul water */ + pline_The("water is foul! You gag and vomit."); + morehungry(rn1(20, 11)); + vomit(); + break; + case 21: /* Poisonous */ + pline_The("water is contaminated!"); + if (Poison_resistance) { + pline("Perhaps it is runoff from the nearby %s farm.", + fruitname(FALSE)); + losehp(rnd(4), "unrefrigerated sip of juice", KILLED_BY_AN); + break; + } + poison_strdmg(rn1(4, 3), rnd(10), "contaminated water", + KILLED_BY); + exercise(A_CON, FALSE); + break; + case 22: /* Fountain of snakes! */ + dowatersnakes(); + break; + case 23: /* Water demon */ + dowaterdemon(); + break; + case 24: { /* Maybe curse some items */ + register struct obj *obj; + int buc_changed = 0; + + pline("This water's no good!"); + morehungry(rn1(20, 11)); + exercise(A_CON, FALSE); + /* this is more severe than rndcurse() */ + for (obj = gi.invent; obj; obj = obj->nobj) + if (obj->oclass != COIN_CLASS && !obj->cursed && !rn2(5)) { + curse(obj); + ++buc_changed; + } + if (buc_changed) + update_inventory(); + break; + } + case 25: /* See invisible */ + if (Blind) { + if (Invisible) { + You("feel transparent."); + } else { + You("feel very self-conscious."); + pline("Then it passes."); + } + } else { + You_see("an image of someone stalking you."); + pline("But it disappears."); + } + HSee_invisible |= FROMOUTSIDE; + newsym(u.ux, u.uy); + exercise(A_WIS, TRUE); + break; + case 26: /* See Monsters */ + if (monster_detect((struct obj *) 0, 0)) + pline_The("%s tastes like nothing.", hliquid("water")); + exercise(A_WIS, TRUE); + break; + case 27: /* Find a gem in the sparkling waters. */ + if (!FOUNTAIN_IS_LOOTED(u.ux, u.uy)) { + dofindgem(); + break; + } + /*FALLTHRU*/ + case 28: /* Water Nymph */ + dowaternymph(); + break; + case 29: /* Scare */ + { + register struct monst *mtmp; + + pline("This %s gives you bad breath!", + hliquid("water")); + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + if (DEADMONSTER(mtmp)) + continue; + monflee(mtmp, 0, FALSE, FALSE); + } + break; + } + case 30: /* Gushing forth in this room */ + dogushforth(TRUE); + break; + default: + pline("This tepid %s is tasteless.", + hliquid("water")); + break; + } + } + dryup(u.ux, u.uy, TRUE); +} + +void +dipfountain(register struct obj *obj) +{ + int er = ER_NOTHING; + + if (Levitation) { + floating_above("fountain"); + return; + } + + if (obj->otyp == LONG_SWORD && u.ulevel >= 5 && !rn2(6) + /* once upon a time it was possible to poly N daggers into N swords */ + && obj->quan == 1L && !obj->oartifact + && !exist_artifact(LONG_SWORD, artiname(ART_EXCALIBUR))) { + static const char lady[] = "Lady of the Lake"; + + if (u.ualign.type != A_LAWFUL) { + /* Ha! Trying to cheat her. */ + pline("A freezing mist rises from the %s and envelopes the sword.", + hliquid("water")); + pline_The("fountain disappears!"); + curse(obj); + if (obj->spe > -6 && !rn2(3)) + obj->spe--; + obj->oerodeproof = FALSE; + exercise(A_WIS, FALSE); + livelog_printf(LL_ARTIFACT, + "was denied %s! The %s has deemed %s unworthy", + artiname(ART_EXCALIBUR), lady, uhim()); + } else { + /* The lady of the lake acts! - Eric Backus */ + /* Be *REAL* nice */ + pline( + "From the murky depths, a hand reaches up to bless the sword."); + pline("As the hand retreats, the fountain disappears!"); + obj = oname(obj, artiname(ART_EXCALIBUR), + ONAME_VIA_DIP | ONAME_KNOW_ARTI); + discover_artifact(ART_EXCALIBUR); + bless(obj); + obj->oeroded = obj->oeroded2 = 0; + obj->oerodeproof = TRUE; + exercise(A_WIS, TRUE); + livelog_printf(LL_ARTIFACT, "was given %s by the %s", + artiname(ART_EXCALIBUR), lady); + } + update_inventory(); + set_levltyp(u.ux, u.uy, ROOM); /* updates level.flags.nfountains */ + levl[u.ux][u.uy].flags = 0; + newsym(u.ux, u.uy); + if (in_town(u.ux, u.uy)) + (void) angry_guards(FALSE); + return; + } else { + er = water_damage(obj, NULL, TRUE); + + if (er == ER_DESTROYED || (er != ER_NOTHING && !rn2(2))) { + return; /* no further effect */ + } + } + + switch (rnd(30)) { + case 16: /* Curse the item */ + if (obj->oclass != COIN_CLASS && !obj->cursed) { + curse(obj); + } + break; + case 17: + case 18: + case 19: + case 20: /* Uncurse the item */ + if (obj->cursed) { + if (!Blind) + pline_The("%s glows for a moment.", hliquid("water")); + uncurse(obj); + } else { + pline("A feeling of loss comes over you."); + } + break; + case 21: /* Water Demon */ + dowaterdemon(); + break; + case 22: /* Water Nymph */ + dowaternymph(); + break; + case 23: /* an Endless Stream of Snakes */ + dowatersnakes(); + break; + case 24: /* Find a gem */ + if (!FOUNTAIN_IS_LOOTED(u.ux, u.uy)) { + dofindgem(); + break; + } + /*FALLTHRU*/ + case 25: /* Water gushes forth */ + dogushforth(FALSE); + break; + case 26: /* Strange feeling */ + pline("A strange tingling runs up your %s.", body_part(ARM)); + break; + case 27: /* Strange feeling */ + You_feel("a sudden chill."); + break; + case 28: /* Strange feeling */ + pline("An urge to take a bath overwhelms you."); + { + long money = money_cnt(gi.invent); + struct obj *otmp; + if (money > 10) { + /* Amount to lose. Might get rounded up as fountains don't + * pay change... */ + money = somegold(money) / 10; + for (otmp = gi.invent; otmp && money > 0; otmp = otmp->nobj) + if (otmp->oclass == COIN_CLASS) { + int denomination = objects[otmp->otyp].oc_cost; + long coin_loss = + (money + denomination - 1) / denomination; + coin_loss = min(coin_loss, otmp->quan); + otmp->quan -= coin_loss; + money -= coin_loss * denomination; + if (!otmp->quan) + delobj(otmp); + } + You("lost some of your gold in the fountain!"); + CLEAR_FOUNTAIN_LOOTED(u.ux, u.uy); + exercise(A_WIS, FALSE); + } + } + break; + case 29: /* You see coins */ + /* We make fountains have more coins the closer you are to the + * surface. After all, there will have been more people going + * by. Just like a shopping mall! Chris Woodbury */ + + if (FOUNTAIN_IS_LOOTED(u.ux, u.uy)) + break; + SET_FOUNTAIN_LOOTED(u.ux, u.uy); + (void) mkgold((long) (rnd((dunlevs_in_dungeon(&u.uz) - dunlev(&u.uz) + + 1) * 2) + 5), + u.ux, u.uy); + if (!Blind) + pline("Far below you, you see coins glistening in the %s.", + hliquid("water")); + exercise(A_WIS, TRUE); + newsym(u.ux, u.uy); + break; + default: + if (er == ER_NOTHING) + pline("Nothing seems to happen."); + break; + } + update_inventory(); + dryup(u.ux, u.uy, TRUE); +} + +void +breaksink(coordxy x, coordxy y) +{ + if (cansee(x, y) || u_at(x, y)) + pline_The("pipes break! Water spurts out!"); + /* updates level.flags.nsinks and level.flags.nfountains */ + set_levltyp(x, y, FOUNTAIN); + levl[x][y].looted = 0; + levl[x][y].blessedftn = 0; + SET_FOUNTAIN_LOOTED(x, y); + newsym(x, y); +} + +void +drinksink(void) +{ + struct obj *otmp; + struct monst *mtmp; + + /* If the player is levitating, they can't drink from the sink. */ + if (Levitation) { + floating_above("sink"); + return; + } + + /* Randomly determine what happens when the player drinks from the sink. */ + switch (rn2(20)) { + case 0: + You("take a sip of very cold %s.", hliquid("water")); + break; + case 1: + You("take a sip of very warm %s.", hliquid("water")); + break; + case 2: + You("take a sip of scalding hot %s.", hliquid("water")); + if (Fire_resistance) { + pline("It seems quite tasty."); + monstseesu(M_SEEN_FIRE); + } else { + losehp(rnd(6), "sipping boiling water", KILLED_BY); + monstunseesu(M_SEEN_FIRE); + } + /* Boiling water burns are considered fire damage. */ + break; + case 3: + /* Spawn a sewer rat in the sink, unless they're already gone. */ + if (gm.mvitals[PM_SEWER_RAT].mvflags & G_GONE) + pline_The("sink seems quite dirty."); + else { + mtmp = makemon(&mons[PM_SEWER_RAT], u.ux, u.uy, MM_NOMSG); + if (mtmp) + pline("Eek! There's %s in the sink!", + (Blind || !canspotmon(mtmp)) ? "something squirmy" + : a_monnam(mtmp)); + } + break; + case 4: + /* Spawn a potion that isn't water. */ + for (;;) { + otmp = mkobj(POTION_CLASS, FALSE); + if (otmp->otyp != POT_WATER) + break; + /* Reject water and try again. */ + obfree(otmp, (struct obj *) 0); + } + otmp->cursed = otmp->blessed = 0; + pline("Some %s liquid flows from the faucet.", + Blind ? "odd" : hcolor(OBJ_DESCR(objects[otmp->otyp]))); + otmp->dknown = !(Blind || Hallucination); + otmp->quan++; /* Avoid panic upon useup() */ + otmp->fromsink = 1; /* Kludge for docall() */ + (void) dopotion(otmp); + obfree(otmp, (struct obj *) 0); + break; + case 5: + /* Spawn a ring in the sink, unless one has already been looted. */ + if (!(levl[u.ux][u.uy].looted & S_LRING)) { + 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; + case 6: + /* Break the sink. */ + breaksink(u.ux, u.uy); + break; + case 7: + /* Spawn a water elemental in the sink. */ + pline_The("%s moves as though of its own will!", hliquid("water")); + if ((gm.mvitals[PM_WATER_ELEMENTAL].mvflags & G_GONE) + || !makemon(&mons[PM_WATER_ELEMENTAL], u.ux, u.uy, MM_NOMSG)) + pline("But it quiets down."); + break; + case 8: + /* Drinking the water gives the player experience and a level-up. */ + pline("Yuk, this %s tastes awful.", hliquid("water")); + more_experienced(1, 0); + newexplevel(); + break; + case 9: + /* Drinking sewage causes the player to vomit and become hungry. */ + pline("Gaggg... this tastes like sewage! You vomit."); + morehungry(rn1(30 - ACURR(A_CON), 11)); + vomit(); + break; + case 10: + /* Drinking toxic waste polymorphs the player. */ + pline("This %s contains toxic wastes!", hliquid("water")); + if (!Unchanging) { + You("undergo a freakish metamorphosis!"); + polyself(POLY_NOFLAGS); + } + break; + /* More odd messages. */ + case 11: + Soundeffect(se_clanking_pipe, 50); + You_hear("clanking from the pipes..."); + break; + case 12: + Soundeffect(se_sewer_song, 100); + You_hear("snatches of song from among the sewers..."); + break; + case 13: + /* Drinking the water creates a gas cloud. */ + pline("Ew, what a stench!"); + create_gas_cloud(u.ux, u.uy, 1, 4); + break; + case 19: + /* Drinking the water causes something to grab at the player's hand, unless they're hallucinating. */ + if (Hallucination) { + pline("From the murky drain, a hand reaches up... --oops--"); + break; + } + /*FALLTHRU*/ + default: + /* Drinking the water has no special effect. */ + You("take a sip of %s %s.", + rn2(3) ? (rn2(2) ? "cold" : "warm") : "hot", + hliquid("water")); + } +} + +/*fountain.c*/