/* 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*/