/* Wake every monster in range... */ void awaken_monsters(struct monst *mon, int distance) { struct monst *mtmp; int distm; for (mtmp = level->monlist; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp) || mon == mtmp) continue; distm = dist2(m_mx(mon), m_my(mon), mtmp->mx, mtmp->my); if (distm >= distance || mon == mtmp) continue; mtmp->msleeping = 0; mtmp->mcanmove = 1; mtmp->mfrozen = 0; /* May scare some monsters */ if (distm >= distance / 3 || resist(mon, mtmp, TOOL_CLASS, NOTELL, 0)) continue; monflee(mtmp, 0, FALSE, TRUE); } /* affect the player */ distm = dist2(m_mx(mon), m_my(mon), youmonst.mx, youmonst.my); if (distm >= distance || mon == &youmonst) return; cancel_helplessness(hm_unconscious, "Surprised, you are jolted into full consciousness!"); if (distm >= distance / 3) return; /* there is no real player scare effect, so make it work similar to potion ghosts, except only 2 turns. Until an actual player scared effect can be made, monsters utilizing the paralyzation "intelligently" isn't fair game -- it currently only happens if a monster is about to discover that a magical instrument horn is out of charges */ if (mon != &youmonst) { pline(msgc_statusbad, "The loud noise frightens you momentarily and you are unable to move!"); helpless(2, hr_afraid, "being frightened", "You regain your composure."); } }
static int domonnoise(struct monst *mtmp) { const char *pline_msg = 0, /* Monnam(mtmp) will be prepended */ *verbl_msg = 0; /* verbalize() */ const struct permonst *ptr = mtmp->data; /* presumably nearness checks have already been made */ if (!canhear()) return 0; if (is_silent(ptr)) return 0; /* Make sure its your role's quest quardian; adjust if not */ if (ptr->msound == MS_GUARDIAN && ptr != &pm_guardian) { int mndx = monsndx(ptr); ptr = &mons[genus(mndx, 1)]; } /* be sure to do this before talking; the monster might teleport away, in which case we want to check its pre-teleport position */ if (!canspotmon(mtmp)) map_invisible(mtmp->mx, mtmp->my); switch (ptr->msound) { case MS_ORACLE: return doconsult(mtmp); case MS_PRIEST: priest_talk(mtmp); break; case MS_LEADER: case MS_NEMESIS: case MS_GUARDIAN: quest_chat(mtmp); break; case MS_SELL: /* pitch, pay, total */ shk_chat(mtmp); break; case MS_VAMPIRE: { /* vampire messages are varied by tameness, peacefulness, and time of night */ boolean isnight = night(); boolean kindred = (Upolyd && (u.umonnum == PM_VAMPIRE || u.umonnum == PM_VAMPIRE_LORD)); boolean nightchild = (Upolyd && (u.umonnum == PM_WOLF || u.umonnum == PM_WINTER_WOLF || u.umonnum == PM_WINTER_WOLF_CUB)); const char *racenoun = (u.ufemale && urace.individual.f) ? urace. individual.f : (urace.individual.m) ? urace.individual. m : urace.noun; if (mtmp->mtame) { if (kindred) verbl_msg = msgprintf("Good %s to you Master%s", isnight ? "evening" : "day", isnight ? "!" : ". Why do we not rest?"); else verbl_msg = msgcat( nightchild ? "Child of the night, " : "", midnight()? "I can stand this craving no longer!" : isnight ? "I beg you, help me satisfy this growing craving!" : "I find myself growing a little weary."); } else if (mtmp->mpeaceful) { if (kindred && isnight) verbl_msg = msgprintf("Good feeding %s!", u.ufemale ? "sister" : "brother"); else if (nightchild && isnight) verbl_msg = "How nice to hear you, child of the night!"; else verbl_msg = "I only drink... potions."; } else { int vampindex; static const char *const vampmsg[] = { /* These first two (0 and 1) are specially handled below */ "I vant to suck your %s!", "I vill come after %s without regret!", /* other famous vampire quotes can follow here if desired */ }; if (kindred) verbl_msg = "This is my hunting ground that you dare to prowl!"; else if (youmonst.data == &mons[PM_SILVER_DRAGON] || youmonst.data == &mons[PM_BABY_SILVER_DRAGON]) { /* Silver dragons are silver in color, not made of silver */ verbl_msg = msgprintf( "%s! Your silver sheen does not frighten me!", youmonst.data == &mons[PM_SILVER_DRAGON] ? "Fool" : "Young Fool"); } else { vampindex = rn2(SIZE(vampmsg)); if (vampindex == 0) { verbl_msg = msgprintf( vampmsg[vampindex], body_part(BLOOD)); } else if (vampindex == 1) { verbl_msg = msgprintf( vampmsg[vampindex], Upolyd ? an(mons[u.umonnum].mname) : an(racenoun)); } else verbl_msg = vampmsg[vampindex]; } } } break; case MS_WERE: if (flags.moonphase == FULL_MOON && (night() ^ !rn2(13))) { pline("%s throws back %s head and lets out a blood curdling %s!", Monnam(mtmp), mhis(mtmp), ptr == &mons[PM_HUMAN_WERERAT] ? "shriek" : "howl"); wake_nearto(mtmp->mx, mtmp->my, 11 * 11); } else pline_msg = "whispers inaudibly. All you can make out is \"moon\"."; break; case MS_BARK: if (flags.moonphase == FULL_MOON && night()) { pline_msg = "howls."; } else if (mtmp->mpeaceful) { if (mtmp->mtame && (mtmp->mconf || mtmp->mflee || mtmp->mtrapped || moves > EDOG(mtmp)->hungrytime || mtmp->mtame < 5)) pline_msg = "whines."; else if (mtmp->mtame && EDOG(mtmp)->hungrytime > moves + 1000) pline_msg = "yips."; else { if (mtmp->data == &mons[PM_FOX]) pline_msg = whatthefoxsays(); else if (mtmp->data != &mons[PM_DINGO]) /* dingos do not actually bark */ pline_msg = "barks."; } } else { if (mtmp->data == &mons[PM_FOX]) pline_msg = whatthefoxsays(); else pline_msg = "growls."; } break; case MS_MEW: if (mtmp->mtame) { if (mtmp->mconf || mtmp->mflee || mtmp->mtrapped || mtmp->mtame < 5) pline_msg = "yowls."; else if (moves > EDOG(mtmp)->hungrytime) pline_msg = "meows."; else if (EDOG(mtmp)->hungrytime > moves + 1000) pline_msg = "purrs."; else pline_msg = "mews."; break; } /* else FALLTHRU */ case MS_GROWL: pline_msg = mtmp->mpeaceful ? "snarls." : "growls!"; break; case MS_ROAR: pline_msg = mtmp->mpeaceful ? "snarls." : "roars!"; break; case MS_SQEEK: pline_msg = "squeaks."; break; case MS_SQAWK: if (ptr == &mons[PM_RAVEN] && !mtmp->mpeaceful) verbl_msg = "Nevermore!"; else pline_msg = "squawks."; break; case MS_HISS: if (!mtmp->mpeaceful) pline_msg = "hisses!"; else return 0; /* no sound */ break; case MS_BUZZ: pline_msg = mtmp->mpeaceful ? "drones." : "buzzes angrily."; break; case MS_GRUNT: pline_msg = "grunts."; break; case MS_NEIGH: if (mtmp->mtame < 5) pline_msg = "neighs."; else if (moves > EDOG(mtmp)->hungrytime) pline_msg = "whinnies."; else pline_msg = "whickers."; break; case MS_WAIL: pline_msg = "wails mournfully."; break; case MS_GURGLE: pline_msg = "gurgles."; break; case MS_BURBLE: pline_msg = "burbles."; break; case MS_SHRIEK: pline_msg = "shrieks."; aggravate(); break; case MS_IMITATE: pline_msg = "imitates you."; break; case MS_BONES: pline("%s rattles noisily.", Monnam(mtmp)); pline("You freeze for a moment."); helpless(2, hr_afraid, "scared by rattling", NULL); break; case MS_LAUGH: { static const char *const laugh_msg[4] = { "giggles.", "chuckles.", "snickers.", "laughs.", }; pline_msg = laugh_msg[rn2(4)]; } break; case MS_MUMBLE: pline_msg = "mumbles incomprehensibly."; break; case MS_WISHGIVER: if (mtmp->mtame) { verbl_msg = "Sorry, I'm all out of wishes."; } else if (mtmp->mpeaceful) { if (ptr == &mons[PM_WATER_DEMON]) pline_msg = "gurgles."; else verbl_msg = "I'm free!"; } else verbl_msg = "This will teach you not to disturb me!"; break; case MS_BOAST: /* giants */ if (!mtmp->mpeaceful) { switch (rn2(4)) { case 0: pline("%s boasts about %s gem collection.", Monnam(mtmp), mhis(mtmp)); break; case 1: pline_msg = "complains about a diet of mutton."; break; default: pline_msg = "shouts \"Fee Fie Foe Foo!\" and guffaws."; wake_nearto(mtmp->mx, mtmp->my, 7 * 7); break; } break; } /* else FALLTHRU */ case MS_HUMANOID: if (!mtmp->mpeaceful) { if (In_endgame(&u.uz) && is_mplayer(ptr)) { mplayer_talk(mtmp); break; } else return 0; /* no sound */ } /* Generic peaceful humanoid behaviour. */ if (mtmp->mflee) pline_msg = "wants nothing to do with you."; else if (mtmp->mhp < mtmp->mhpmax / 4) pline_msg = "moans."; else if (mtmp->mconf || mtmp->mstun) verbl_msg = !rn2(3) ? "Huh?" : rn2(2) ? "What?" : "Eh?"; else if (!mtmp->mcansee) verbl_msg = "I can't see!"; else if (mtmp->mtrapped) { struct trap *t = t_at(level, mtmp->mx, mtmp->my); if (t) t->tseen = 1; verbl_msg = "I'm trapped!"; } else if (mtmp->mhp < mtmp->mhpmax / 2) pline_msg = "asks for a potion of healing."; else if (mtmp->mtame && !mtmp->isminion && moves > EDOG(mtmp)->hungrytime) verbl_msg = "I'm hungry."; /* Specific monsters' interests */ else if (is_elf(ptr)) pline_msg = "curses orcs."; else if (is_dwarf(ptr)) pline_msg = "talks about mining."; else if (likes_magic(ptr)) pline_msg = "talks about spellcraft."; else if (ptr->mlet == S_CENTAUR) pline_msg = "discusses hunting."; else switch (monsndx(ptr)) { case PM_HOBBIT: pline_msg = (mtmp->mhpmax - mtmp->mhp >= 10) ? "complains about unpleasant dungeon conditions." : "asks you about the One Ring."; break; case PM_ARCHEOLOGIST: pline_msg = "describes a recent article in \"Spelunker Today\" " "magazine."; break; case PM_TOURIST: verbl_msg = "Aloha."; break; case PM_PRISONER: verbl_msg = "Thank you for freeing me!"; break; default: pline_msg = "discusses dungeon exploration."; break; } break; case MS_SEDUCE: if (ptr->mlet != S_NYMPH && flags.seduce_enabled && could_seduce(mtmp, &youmonst, NULL) == 1) { doseduce(mtmp); break; } switch ((poly_gender() != (int)mtmp->female) ? rn2(3) : 0) { case 2: verbl_msg = "Hello, sailor."; break; case 1: pline_msg = "comes on to you."; break; default: pline_msg = "cajoles you."; } break; case MS_ARREST: if (mtmp->mpeaceful) verbalize("Just the facts, %s.", u.ufemale ? "Ma'am" : "Sir"); else { static const char *const arrest_msg[3] = { "Anything you say can be used against you.", "You're under arrest!", "Stop in the name of the Law!", }; verbl_msg = arrest_msg[rn2(3)]; } break; case MS_BRIBE: if (mtmp->mpeaceful && !mtmp->mtame) { demon_talk(mtmp); break; } /* fall through */ case MS_CUSS: if (!mtmp->mpeaceful) cuss(mtmp); break; case MS_SPELL: /* deliberately vague, since it's not actually casting any spell */ pline_msg = "seems to mutter a cantrip."; break; case MS_NURSE: if (uwep && (uwep->oclass == WEAPON_CLASS || is_weptool(uwep))) verbl_msg = "Put that weapon away before you hurt someone!"; else if (uarmc || (uarm && !uskin()) || uarmh || uarms || uarmg || uarmf) verbl_msg = Role_if(PM_HEALER) ? "Doc, I can't help you unless you cooperate." : "Please undress so I can examine you."; else if (uarmu) verbl_msg = "Take off your shirt, please."; else verbl_msg = "Relax, this won't hurt a bit."; break; case MS_GUARD: if (money_cnt(invent)) verbl_msg = "Please drop that gold and follow me."; else verbl_msg = "Please follow me."; break; case MS_SOLDIER: { static const char *const soldier_foe_msg[3] = { "Resistance is useless!", "You're dog meat!", "Surrender!", }, *const soldier_pax_msg[3] = { "What lousy pay we're getting here!", "The food's not fit for Orcs!", "My feet hurt, I've been on them all day!", }; verbl_msg = mtmp->mpeaceful ? soldier_pax_msg[rn2(3)] : soldier_foe_msg[rn2(3)]; } break; case MS_RIDER: if (ptr == &mons[PM_DEATH] && !rn2(10)) pline_msg = "is busy reading a copy of Sandman #8."; else verbl_msg = "Who do you think you are, War?"; break; } if (pline_msg) pline("%s %s", Monnam(mtmp), pline_msg); else if (verbl_msg) verbalize("%s", verbl_msg); return 1; }
/* called from check_special_room() when the player enters the temple room */ void intemple(int roomno) { struct monst *priest = findpriest((char)roomno); boolean tended = (priest != NULL); boolean sanctum, can_speak; xchar shrined; const char *msg1, *msg2; if (In_mines(&u.uz) && !historysearch("entered the Minetown temple", TRUE)) historic_event(FALSE, TRUE, "entered the Minetown temple"); if (!temple_occupied(u.urooms0)) { if (tended) { shrined = has_shrine(priest); sanctum = (priest->data == &mons[PM_HIGH_PRIEST] && (shrined & AM_SANCTUM)); can_speak = (priest->mcanmove && !priest->msleeping && canhear()); if (can_speak) { unsigned save_priest = priest->ispriest; /* don't reveal the altar's owner upon temple entry in the endgame; for the Sanctum, the next message names Moloch so suppress the "of Moloch" for him here too */ if (sanctum && !Hallucination) priest->ispriest = 0; pline("%s intones:", canseemon(priest) ? Monnam(priest) : "A nearby voice"); priest->ispriest = save_priest; } msg2 = 0; if (sanctum && CONST_EPRI(priest)->shralign == A_NONE) { if (priest->mpeaceful) { msg1 = "Infidel, you have entered Moloch's Sanctum!"; msg2 = "Be gone!"; msethostility(priest, TRUE, TRUE); } else msg1 = "You desecrate this place by your presence!"; } else { msg1 = msgprintf("Pilgrim, you enter a %s place!", !shrined ? "desecrated" : "sacred"); } if (can_speak) { verbalize("%s", msg1); if (msg2) verbalize("%s", msg2); } if (!sanctum) { /* !tended -> !shrined */ if (!shrined || !p_coaligned(priest) || u.ualign.record <= ALGN_SINNED) pline("You have a%s forbidding feeling...", (!shrined) ? "" : " strange"); else pline("You experience a strange sense of peace."); } } else { switch (rn2(3)) { case 0: pline("You have an eerie feeling..."); break; case 1: pline("You feel like you are being watched."); break; default: pline("A shiver runs down your %s.", body_part(SPINE)); break; } if (!rn2(5)) { struct monst *mtmp; if (!((mtmp = makemon(&mons[PM_GHOST], level, u.ux, u.uy, NO_MM_FLAGS)))) return; if (!Blind || sensemon(mtmp)) pline("An enormous ghost appears next to you!"); else pline("You sense a presence close by!"); msethostility(mtmp, TRUE, TRUE); if (flags.verbose) pline("You are frightened to death, and unable to move."); helpless(3, hr_afraid, "frightened to death", "You regain your composure."); } } } }
int study_book(struct obj *spellbook, const struct nh_cmd_arg *arg) { int booktype = spellbook->otyp; boolean confused = (Confusion != 0); boolean too_hard = FALSE; if (u.uoccupation_progress[tos_book] && !confused && spellbook == u.utracked[tos_book] && /* handle the sequence: start reading, get interrupted, have book become erased somehow, resume reading it */ booktype != SPE_BLANK_PAPER) { if (turnstate.continue_message) pline("You continue your efforts to memorize the spell."); } else { /* Restarting reading the book */ /* KMH -- Simplified this code */ if (booktype == SPE_BLANK_PAPER) { pline("This spellbook is all blank."); makeknown(booktype); return 1; } switch (objects[booktype].oc_level) { case 1: case 2: u.uoccupation_progress[tos_book] = -objects[booktype].oc_delay; break; case 3: case 4: u.uoccupation_progress[tos_book] = -(objects[booktype].oc_level - 1) * objects[booktype].oc_delay; break; case 5: case 6: u.uoccupation_progress[tos_book] = -objects[booktype].oc_level * objects[booktype].oc_delay; break; case 7: u.uoccupation_progress[tos_book] = -8 * objects[booktype].oc_delay; break; default: impossible("Unknown spellbook level %d, book %d;", objects[booktype].oc_level, booktype); return 0; } /* Books are often wiser than their readers (Rus.) */ spellbook->in_use = TRUE; if (!spellbook->blessed && spellbook->otyp != SPE_BOOK_OF_THE_DEAD) { if (spellbook->cursed) { too_hard = TRUE; } else { /* uncursed - chance to fail */ int read_ability = ACURR(A_INT) + 4 + u.ulevel / 2 - 2 * objects[booktype].oc_level + ((ublindf && ublindf->otyp == LENSES) ? 2 : 0); /* only wizards know if a spell is too difficult */ if (Role_if(PM_WIZARD) && read_ability < 20 && !confused) { const char *qbuf; qbuf = msgprintf("This spellbook is %sdifficult to " "comprehend. Continue?", (read_ability < 12 ? "very " : "")); if (yn(qbuf) != 'y') { spellbook->in_use = FALSE; return 1; } } /* it's up to random luck now */ if (rnd(20) > read_ability) { too_hard = TRUE; } } } if (too_hard) { boolean gone = cursed_book(spellbook); helpless(-u.uoccupation_progress[tos_book], hr_paralyzed, "frozen by a spellbook", NULL); u.uoccupation_progress[tos_book] = 0; if (gone || !rn2(3)) { if (!gone) pline("The spellbook crumbles to dust!"); if (!objects[spellbook->otyp].oc_name_known && !objects[spellbook->otyp].oc_uname) docall(spellbook); useup(spellbook); } else spellbook->in_use = FALSE; return 1; } else if (confused) { if (!confused_book(spellbook)) { spellbook->in_use = FALSE; } helpless(-u.uoccupation_progress[tos_book], hr_busy, "absorbed in a spellbook", "You're finally able to put the book down."); u.uoccupation_progress[tos_book] = 0; u.utracked[tos_book] = 0; return 1; } spellbook->in_use = FALSE; pline("You begin to %s the runes.", spellbook->otyp == SPE_BOOK_OF_THE_DEAD ? "recite" : "memorize"); } u.utracked[tos_book] = spellbook; one_occupation_turn(learn, "studying", occ_book); return 1; }
/* Handles one turn of book reading. Returns 1 if unfinished, 0 if finshed. */ static int learn(void) { int i; short booktype; boolean costly = TRUE; boolean already_known = FALSE; int first_unknown = MAXSPELL; int known_spells = 0; const char *splname; /* JDS: lenses give 50% faster reading; 33% smaller read time */ if (u.uoccupation_progress[tos_book] && ublindf && ublindf->otyp == LENSES && rn2(2)) u.uoccupation_progress[tos_book]++; if (Confusion) { /* became confused while learning */ confused_book(u.utracked[tos_book]); u.utracked[tos_book] = 0; /* no longer studying */ helpless(-u.uoccupation_progress[tos_book], hr_busy, "absorbed in a spellbook", "You're finally able to put the book down."); u.uoccupation_progress[tos_book] = 0; return 0; } booktype = u.utracked[tos_book]->otyp; if (booktype == SPE_BOOK_OF_THE_DEAD) { deadbook(u.utracked[tos_book], FALSE); u.utracked[tos_book] = 0; u.uoccupation_progress[tos_book] = 0; return 0; } /* The book might get cursed while we're reading it. In this case, immediately stop reading it, cancel progress, and apply a few turns of helplessness. (3.4.3 applies negative spellbook effects but lets you memorize the spell anyway; this makes no sense. It destroys the spellbook on the "contact poison" result, which makes even less sense.) */ if (u.utracked[tos_book]->cursed) { pline("This book isn't making sense any more."); helpless(rn1(5,5), hr_busy, "making sense of a spellbook", "You give up trying to make sense of the spellbook."); u.uoccupation_progress[tos_book] = 0; u.utracked[tos_book] = 0; return 0; } if (++u.uoccupation_progress[tos_book] < 0) return 1; /* still busy */ if (ACURR(A_WIS) < 12) exercise(A_WIS, TRUE); /* you're studying. */ splname = msgprintf(objects[booktype].oc_name_known ? "\"%s\"" : "the \"%s\" spell", OBJ_NAME(objects[booktype])); for (i = 0; i < MAXSPELL; i++) { if (spellid(i) == booktype) { already_known = TRUE; if (u.utracked[tos_book]->spestudied > MAX_SPELL_STUDY) { pline("This spellbook is too faint to be read any more."); u.utracked[tos_book]->otyp = booktype = SPE_BLANK_PAPER; } else if (spellknow(i) <= 1000) { pline("Your knowledge of %s is keener.", splname); incrnknow(i); u.utracked[tos_book]->spestudied++; if (ACURR(A_WIS) < 12) exercise(A_WIS, TRUE); /* extra study */ } else { /* 1000 < spellknow(i) <= MAX_SPELL_STUDY */ pline("You know %s quite well already.", splname); if (yn("Do you want to read the book anyway?") == 'y') { pline("You refresh your knowledge of %s.", splname); incrnknow(i); u.utracked[tos_book]->spestudied++; } else costly = FALSE; } /* make book become known even when spell is already known, in case amnesia made you forget the book */ makeknown((int)booktype); break; } else if (spellid(i) == NO_SPELL && (i < first_unknown || i == spellno_from_let(objects[booktype].oc_defletter))) first_unknown = i; else known_spells++; } if (first_unknown == MAXSPELL && !already_known) panic("Too many spells memorized!"); if (!already_known) { spl_book[first_unknown].sp_id = booktype; spl_book[first_unknown].sp_lev = objects[booktype].oc_level; incrnknow(first_unknown); u.utracked[tos_book]->spestudied++; pline(known_spells > 0 ? "You add %s to your repertoire." : "You learn %s.", splname); makeknown((int)booktype); } if (costly) check_unpaid(u.utracked[tos_book]); u.utracked[tos_book] = 0; return 0; }
/* Note: I had to choose one of three possible kinds of "type" when writing * this function: a wand type (like in zap.c), an adtyp, or an object type. * Wand types get complex because they must be converted to adtyps for * determining such things as fire resistance. Adtyps get complex in that * they don't supply enough information--was it a player or a monster that * did it, and with a wand, spell, or breath weapon? Object types share both * these disadvantages.... * * The descr argument should be used to describe the explosion. It should be * a string suitable for use with an(). * raylevel is used for explosions caused by skilled wand usage (0=no wand) */ void explode(int x, int y, int type, /* the same as in zap.c */ int dam, char olet, int expltype, const char *descr, int raylevel) { int i, j, k, damu = dam; boolean visible, any_shield, resist_death; resist_death = FALSE; int uhurt = 0; /* 0=unhurt, 1=items damaged, 2=you and items damaged */ const char *str; const char *dispbuf = ""; /* lint suppression; I think the code's OK */ boolean expl_needs_the = TRUE; int idamres, idamnonres; struct monst *mtmp; uchar adtyp; int explmask[3][3]; /* 0=normal explosion, 1=do shieldeff, 2=do nothing */ boolean shopdamage = FALSE; #if 0 /* Damage reduction from wand explosions */ if (olet == WAND_CLASS) /* retributive strike */ switch (Role_switch) { case PM_PRIEST: case PM_MONK: case PM_WIZARD: damu /= 5; break; case PM_HEALER: case PM_KNIGHT: damu /= 2; break; default: break; } #endif if (olet == MON_EXPLODE) { str = descr; adtyp = AD_PHYS; if (Hallucination) { int name = rndmonidx(); dispbuf = msgcat(s_suffix(monnam_for_index(name)), " explosion"); expl_needs_the = !monnam_is_pname(name); } else { dispbuf = str; } } else { int whattype = abs(type) % 10; adtyp = whattype + 1; boolean done = FALSE, hallu = Hallucination; if (hallu) { do { whattype = rn2(8); } while (whattype == 3); } tryagain: switch (whattype) { case 0: str = "magical blast"; break; case 1: str = olet == BURNING_OIL ? "burning oil" : olet == SCROLL_CLASS ? "tower of flame" : "fireball"; break; case 2: str = "ball of cold"; break; case 3: str = "sleeping gas"; break; case 4: str = (olet == WAND_CLASS) ? "death field" : "disintegration field"; break; case 5: str = "ball of lightning"; break; case 6: str = "poison gas cloud"; break; case 7: str = "splash of acid"; break; default: impossible("explosion base type %d?", type); return; } if (!done) { dispbuf = str; done = TRUE; if (hallu) { whattype = adtyp - 1; goto tryagain; } } } any_shield = visible = FALSE; for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { if (!isok(i + x - 1, j + y - 1)) { explmask[i][j] = 2; continue; } else explmask[i][j] = 0; if (i + x - 1 == u.ux && j + y - 1 == u.uy) { switch (adtyp) { case AD_PHYS: explmask[i][j] = 0; break; case AD_MAGM: explmask[i][j] = !!(raylevel >= P_EXPERT || Antimagic); break; case AD_FIRE: explmask[i][j] = !!Fire_resistance; break; case AD_COLD: explmask[i][j] = !!Cold_resistance; break; case AD_SLEE: explmask[i][j] = !!Sleep_resistance; break; case AD_DISN: if (raylevel == P_UNSKILLED && Drain_resistance) resist_death = TRUE; /* why MR doesn't resist general deathfields is beyond me, but... */ if (nonliving(youmonst.data) || is_demon(youmonst.data)) resist_death = TRUE; if (raylevel && Antimagic) resist_death = TRUE; if (raylevel >= P_EXPERT && !Drain_resistance) resist_death = FALSE; explmask[i][j] = (olet == WAND_CLASS) ? !!resist_death : !!Disint_resistance; break; case AD_ELEC: explmask[i][j] = !!Shock_resistance; break; case AD_DRST: explmask[i][j] = !!Poison_resistance; break; case AD_ACID: explmask[i][j] = !!Acid_resistance; break; default: impossible("explosion type %d?", adtyp); break; } } /* can be both you and mtmp if you're swallowed */ mtmp = m_at(level, i + x - 1, j + y - 1); if (!mtmp && i + x - 1 == u.ux && j + y - 1 == u.uy) mtmp = u.usteed; if (mtmp) { if (mtmp->mhp < 1) explmask[i][j] = 2; else switch (adtyp) { case AD_PHYS: break; case AD_MAGM: explmask[i][j] |= (raylevel >= 4 || resists_magm(mtmp)); break; case AD_FIRE: explmask[i][j] |= resists_fire(mtmp); break; case AD_COLD: explmask[i][j] |= resists_cold(mtmp); break; case AD_SLEE: explmask[i][j] |= resists_sleep(mtmp); case AD_DISN: if (raylevel == P_UNSKILLED && resists_drli(mtmp)) resist_death = TRUE; if (nonliving(mtmp->data) || is_demon(mtmp->data)) resist_death = TRUE; if (raylevel && resists_magm(mtmp)) resist_death = TRUE; if (raylevel >= P_EXPERT && !resists_drli(mtmp)) resist_death = FALSE; explmask[i][j] |= (olet == WAND_CLASS) ? resist_death : resists_disint(mtmp); break; case AD_ELEC: explmask[i][j] |= resists_elec(mtmp); break; case AD_DRST: explmask[i][j] |= resists_poison(mtmp); break; case AD_ACID: explmask[i][j] |= resists_acid(mtmp); break; default: impossible("explosion type %d?", adtyp); break; } } reveal_monster_at(i + x - 1, j + y - 1, TRUE); if (cansee(i + x - 1, j + y - 1)) visible = TRUE; if (explmask[i][j] == 1) any_shield = TRUE; } if (visible) { struct tmp_sym *tsym = tmpsym_init(DISP_BEAM, 0); /* Start the explosion */ for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { if (explmask[i][j] == 2) continue; tmpsym_change(tsym, dbuf_explosion(expltype, explosion[i][j])); tmpsym_at(tsym, i + x - 1, j + y - 1); } flush_screen(); /* will flush screen and output */ if (any_shield && flags.sparkle) { /* simulate shield effect */ for (k = 0; k < SHIELD_COUNT; k++) { for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { if (explmask[i][j] == 1) /* * Bypass tmpsym_at() and send the shield glyphs * directly to the buffered screen. tmpsym_at() * will clean up the location for us later. */ dbuf_set_effect(i + x - 1, j + y - 1, dbuf_effect(E_MISC, shield_static[k])); } flush_screen(); /* will flush screen and output */ win_delay_output(); } /* Cover last shield glyph with blast symbol. */ for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { if (explmask[i][j] == 1) dbuf_set_effect(i + x - 1, j + y - 1, dbuf_explosion(expltype, explosion[i][j])); } } else { /* delay a little bit. */ win_delay_output(); win_delay_output(); } tmpsym_end(tsym); /* clear the explosion */ } else { if (olet == MON_EXPLODE) { str = "explosion"; } You_hear("a blast."); } if (dam) for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { if (explmask[i][j] == 2) continue; if (i + x - 1 == u.ux && j + y - 1 == u.uy) uhurt = (explmask[i][j] == 1) ? 1 : 2; idamres = idamnonres = 0; if (type >= 0) zap_over_floor((xchar) (i + x - 1), (xchar) (j + y - 1), type, &shopdamage); mtmp = m_at(level, i + x - 1, j + y - 1); if (!mtmp && i + x - 1 == u.ux && j + y - 1 == u.uy) mtmp = u.usteed; if (!mtmp) continue; if (Engulfed && mtmp == u.ustuck) { if (is_animal(u.ustuck->data)) pline("%s gets %s!", Monnam(u.ustuck), (adtyp == AD_FIRE) ? "heartburn" : (adtyp == AD_COLD) ? "chilly" : (adtyp == AD_DISN) ? ((olet == WAND_CLASS) ? "irradiated by pure energy" : "perforated") : (adtyp == AD_ELEC) ? "shocked" : (adtyp == AD_DRST) ? "poisoned" : (adtyp == AD_ACID) ? "an upset stomach" : "fried"); else pline("%s gets slightly %s!", Monnam(u.ustuck), (adtyp == AD_FIRE) ? "toasted" : (adtyp == AD_COLD) ? "chilly" : (adtyp == AD_DISN) ? ((olet == WAND_CLASS) ? "overwhelmed by pure energy" : "perforated") : (adtyp == AD_ELEC) ? "shocked" : (adtyp == AD_DRST) ? "intoxicated" : (adtyp == AD_ACID) ? "burned" : "fried"); } else if (cansee(i + x - 1, j + y - 1)) { if (mtmp->m_ap_type) seemimic(mtmp); pline("%s is caught in %s%s!", Monnam(mtmp), expl_needs_the ? "the " : "", dispbuf); } idamres += destroy_mitem(mtmp, SCROLL_CLASS, (int)adtyp); idamres += destroy_mitem(mtmp, SPBOOK_CLASS, (int)adtyp); idamnonres += destroy_mitem(mtmp, POTION_CLASS, (int)adtyp); idamnonres += destroy_mitem(mtmp, WAND_CLASS, (int)adtyp); idamnonres += destroy_mitem(mtmp, RING_CLASS, (int)adtyp); if (explmask[i][j] == 1) { golemeffects(mtmp, (int)adtyp, dam + idamres); mtmp->mhp -= idamnonres; } else { /* call resist with 0 and do damage manually so 1) we can get out the message before doing the damage, and 2) we can call mondied, not killed, if it's not your blast */ int mdam = dam; if (resist(mtmp, olet, 0, FALSE)) { if (cansee(i + x - 1, j + y - 1)) pline("%s resists %s%s!", Monnam(mtmp), expl_needs_the ? "the " : "", dispbuf); mdam = dam / 2; } if (mtmp == u.ustuck) mdam *= 2; if (resists_cold(mtmp) && adtyp == AD_FIRE) mdam *= 2; else if (resists_fire(mtmp) && adtyp == AD_COLD) mdam *= 2; if (adtyp == AD_MAGM && raylevel >= P_EXPERT && resists_magm(mtmp)) mdam = (mdam + 1) / 2; if (adtyp == AD_SLEE && raylevel) { sleep_monst(mtmp, mdam, WAND_CLASS); mdam = 0; } if (adtyp == AD_DISN && raylevel) { if (nonliving(mtmp->data) || is_demon(mtmp->data) || resists_magm(mtmp) || raylevel == P_UNSKILLED) { /* monster is deathresistant or raylevel==unskilled, since monster apparently failed to resist earlier, monster must be vulnerable to drli */ /* FIXME: make a generic losexp() for monsters */ mdam = dice(2, 6); if (cansee(i + x - 1, j + y - 1)) pline("%s suddenly seems weaker!", Monnam(mtmp)); mtmp->mhpmax -= mdam; if (mtmp->m_lev == 0) mdam = mtmp->mhp; else mtmp->m_lev--; } else mdam = mtmp->mhp; /* instadeath */ } mtmp->mhp -= mdam; mtmp->mhp -= (idamres + idamnonres); } if (mtmp->mhp <= 0) { /* KMH -- Don't blame the player for pets killing gas spores */ if (!flags.mon_moving) killed(mtmp); else monkilled(mtmp, "", (int)adtyp); } else if (!flags.mon_moving) setmangry(mtmp); } /* Do your injury last */ if (uhurt) { if ((type >= 0 || adtyp == AD_PHYS) && /* gas spores */ flags.verbose && olet != SCROLL_CLASS) pline("You are caught in %s%s!", expl_needs_the ? "the " : "", dispbuf); /* do property damage first, in case we end up leaving bones */ if (adtyp == AD_FIRE) burn_away_slime(); if (u.uinvulnerable) { damu = 0; pline("You are unharmed!"); } else if (Half_physical_damage && adtyp == AD_PHYS) damu = (damu + 1) / 2; else if (raylevel) { if (adtyp == AD_MAGM && Antimagic) damu = (damu + 1) / 2; if (adtyp == AD_SLEE) { helpless(damu, hr_asleep, "sleeping", NULL); damu = 0; } if (adtyp == AD_DISN) { if (nonliving(youmonst.data) || is_demon(youmonst.data) || Antimagic || raylevel == P_UNSKILLED) { losexp("drained by a death field",FALSE); damu = 0; } else { done(DIED, "killed by a death field"); damu = 0; /* lifesaved */ } } } if (adtyp == AD_FIRE) { burnarmor(&youmonst); set_candles_afire(); } destroy_item(SCROLL_CLASS, (int)adtyp); destroy_item(SPBOOK_CLASS, (int)adtyp); destroy_item(POTION_CLASS, (int)adtyp); destroy_item(RING_CLASS, (int)adtyp); destroy_item(WAND_CLASS, (int)adtyp); ugolemeffects((int)adtyp, damu); if (uhurt == 2) { if (Upolyd) u.mh -= damu; else u.uhp -= damu; } if (u.uhp <= 0 || (Upolyd && u.mh <= 0)) { int death = adtyp == AD_FIRE ? BURNING : DIED; const char *killer; if (olet == MON_EXPLODE) { killer = killer_msg(death, an(str)); } else if (type >= 0 && olet != SCROLL_CLASS) { /* check whether or not we were the source of the explosion */ if (!flags.mon_moving) killer = msgprintf("caught %sself in %s own %s", uhim(), uhis(), str); else killer = msgprintf("killed by a %s", str); } else if (!strcmp(str, "burning oil")) { /* This manual check hack really sucks */ killer = killer_msg(death, str); } else { killer = killer_msg(death, an(str)); } /* Known BUG: BURNING suppresses corpse in bones data, but done does not handle killer reason correctly */ if (Upolyd) { rehumanize(death, killer); } else { done(death, killer); } } exercise(A_STR, FALSE); } if (shopdamage) { pay_for_damage(adtyp == AD_FIRE ? "burn away" : adtyp == AD_COLD ? "shatter" : adtyp == AD_DISN ? "disintegrate" : "destroy", FALSE); } /* explosions are noisy */ i = dam * dam; if (i < 50) i = 50; /* in case random damage is very small */ wake_nearto(x, y, i); }
/* Returns 1 when something was stolen (or at least, when N should flee now) * Returns -1 if the monster died in the attempt * Avoid stealing the object stealoid */ int steal(struct monst *mtmp, const char **objnambuf) { struct obj *otmp; int tmp, could_petrify, named = 0, armordelay, slowly = 0; boolean monkey_business; /* true iff an animal is doing the thievery */ if (objnambuf) *objnambuf = ""; /* the following is true if successful on first of two attacks. */ if (!monnear(mtmp, u.ux, u.uy)) return 0; if (!invent || (inv_cnt(FALSE) == 1 && uskin())) { nothing_to_steal: /* Not even a thousand men in armor can strip a naked man. */ if (Blind) pline("Somebody tries to rob you, but finds nothing to steal."); else pline("%s tries to rob you, but there is nothing to steal!", Monnam(mtmp)); return 1; /* let her flee */ } monkey_business = is_animal(mtmp->data); if (monkey_business) { ; /* skip ring special cases */ } else if (Adornment & W_MASK(os_ringl)) { otmp = uleft; goto gotobj; } else if (Adornment & W_MASK(os_ringr)) { otmp = uright; goto gotobj; } tmp = 0; for (otmp = invent; otmp; otmp = otmp->nobj) if ((!uarm || otmp != uarmc) && otmp != uskin() #ifdef INVISIBLE_OBJECTS && (!otmp->oinvis || perceives(mtmp->data)) #endif ) tmp += ((otmp->owornmask & W_WORN) ? 5 : 1); if (!tmp) goto nothing_to_steal; tmp = rn2(tmp); for (otmp = invent; otmp; otmp = otmp->nobj) if ((!uarm || otmp != uarmc) && otmp != uskin() #ifdef INVISIBLE_OBJECTS && (!otmp->oinvis || perceives(mtmp->data)) #endif ) if ((tmp -= ((otmp->owornmask & W_WORN) ? 5 : 1)) < 0) break; if (!otmp) { impossible("Steal fails!"); return 0; } /* can't steal gloves while wielding - so steal the wielded item. */ if (otmp == uarmg && uwep) otmp = uwep; /* can't steal armor while wearing cloak - so steal the cloak. */ else if (otmp == uarm && uarmc) otmp = uarmc; else if (otmp == uarmu && uarmc) otmp = uarmc; else if (otmp == uarmu && uarm && !uskin()) otmp = uarm; gotobj: /* animals can't overcome curse stickiness nor unlock chains */ if (monkey_business) { boolean ostuck; /* is the player prevented from voluntarily giving up this item? (ignores loadstones; the !can_carry() check will catch those) */ if (otmp == uball) ostuck = TRUE; /* effectively worn; curse is implicit */ else if (otmp == uquiver || (otmp == uswapwep && !u.twoweap)) ostuck = FALSE; /* not really worn; curse doesn't matter */ else ostuck = (otmp->cursed && otmp->owornmask); if (ostuck || !can_carry(mtmp, otmp)) { static const char *const how[] = { "steal", "snatch", "grab", "take" }; cant_take: pline("%s tries to %s your %s but gives up.", Monnam(mtmp), how[rn2(SIZE(how))], (otmp->owornmask & W_ARMOR) ? equipname(otmp) : cxname(otmp)); /* the fewer items you have, the less likely the thief is going to stick around to try again (0) instead of running away (1) */ return !rn2(inv_cnt(FALSE) / 5 + 2); } } if (otmp->otyp == LEASH && otmp->leashmon) { if (monkey_business && otmp->cursed) goto cant_take; o_unleash(otmp); } /* you're going to notice the theft... */ action_interrupted(); if (otmp->owornmask & W_WORN) { switch (otmp->oclass) { case TOOL_CLASS: case AMULET_CLASS: case RING_CLASS: case FOOD_CLASS: /* meat ring */ remove_worn_item(otmp, TRUE); break; case ARMOR_CLASS: armordelay = objects[otmp->otyp].oc_delay; if (monkey_business) { /* animals usually don't have enough patience to take off items which require extra time */ if (armordelay >= 1 && rn2(10)) goto cant_take; remove_worn_item(otmp, TRUE); break; } else { int curssv = otmp->cursed; boolean seen = canspotmon(mtmp); otmp->cursed = 0; /* can't charm you without first waking you */ cancel_helplessness(hm_fainted, "Someone revives you."); slowly = (armordelay >= 1 || u_helpless(hm_all)); if (u_helpless(hm_all)) { pline("%s tries to %s you, but is dismayed by your lack of " "response.", !seen ? "She" : Monnam(mtmp), u.ufemale ? "charm" : "seduce"); return (0); } if (u.ufemale) pline("%s charms you. You gladly %s your %s.", !seen ? "She" : Monnam(mtmp), curssv ? "let her take" : slowly ? "start removing" : "hand over", equipname(otmp)); else pline("%s seduces you and %s off your %s.", !seen ? "She" : Adjmonnam(mtmp, "beautiful"), curssv ? "helps you to take" : slowly ? "you start taking" : "you take", equipname(otmp)); named++; if (armordelay) helpless(armordelay, hr_busy, "taking off clothes", "You finish disrobing."); remove_worn_item(otmp, TRUE); otmp->cursed = curssv; /* Note: it used to be that the nymph would wait for you to disrobe, then take the item, but that lead to huge complications in the code (and a rather unfun situation where the nymph could chain armor theft), and some resulting bugs. Instead, we just go down the normal codepath; you lose the item, and you're left helpless for the length of time it should have taken to remove. The nymph will stay around (due to the slowly || u_helpless(hm_all) check at the end of the function). */ } break; default: impossible("Tried to steal a strange worn thing. [%d]", otmp->oclass); } } else if (otmp->owornmask) remove_worn_item(otmp, TRUE); /* do this before removing it from inventory */ if (objnambuf) *objnambuf = yname(otmp); /* set mavenge bit so knights won't suffer an alignment penalty during retaliation; */ mtmp->mavenge = 1; freeinv(otmp); pline("%s stole %s.", named ? "She" : Monnam(mtmp), doname(otmp)); could_petrify = (otmp->otyp == CORPSE && touch_petrifies(&mons[otmp->corpsenm])); mpickobj(mtmp, otmp); /* may free otmp */ if (could_petrify && !(mtmp->misc_worn_check & W_MASK(os_armg))) { minstapetrify(mtmp, TRUE); return -1; } return (slowly || u_helpless(hm_all)) ? 0 : 1; }